fixes in dash and autotargetting after testing on the device

This commit is contained in:
2025-05-19 13:54:25 +02:00
parent 247f3e8050
commit cf0e8e0723
10 changed files with 2040 additions and 2176 deletions

View File

@@ -1,312 +1,287 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Beyond;
using Invector.vCharacterController.AI;
using Invector.vCharacterController.AI.FSMBehaviour;
using Beyond; // For Player, GameStateManager, TimeController
using Invector.vCharacterController.AI.FSMBehaviour; // For vFSMBehaviourController
using UnityEngine;
namespace Invector.vMelee
{
using vEventSystems;
using vEventSystems; // For vIAttackListener
public class bMeleeAttackControl : StateMachineBehaviour
{
[Tooltip("normalizedTime of Active Damage")]
[Header("Damage Window")]
[Tooltip("NormalizedTime of Active Damage")]
public float startDamage = 0.05f;
[Tooltip("normalizedTime of Disable Damage")]
[Tooltip("NormalizedTime of Disable Damage")]
public float endDamage = 0.9f;
[Header("Attack Properties")]
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")]
[Tooltip("Body parts to activate for damage detection.")]
public List<string> bodyParts = new List<string> { "RightLowerArm" };
[Header("Hit Effects")]
public bool ignoreDefense;
public bool activeRagdoll;
[vHideInInspector("activeRagdoll"), Tooltip("Time to keep Ragdoll active")]
[vHideInInspector("activeRagdoll")]
[Tooltip("Time to keep Ragdoll active if activeRagdoll is true.")]
public float senselessTime;
[Tooltip("Check true in the last attack of your combo to reset the triggers")]
[Header("Attack Flow")]
[Tooltip("Check true in the last attack of your combo to reset the FSM attack triggers.")]
public bool resetAttackTrigger;
private bool isActive;
public bool debug;
private vIAttackListener mFighter;
private bool isAttacking;
//private bool slowMoActive = false;
public bool useAttackTimeScale = false;
[Tooltip("Normalized time point to reset attack triggers if resetAttackTrigger is true.")]
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;
[Header("Slow Motion Settings")]
[Tooltip("Enable slow motion effect during this attack based on conditions below.")]
public bool useAttackTimeScale = false;
[Tooltip("Distance within which the current auto-target must be for slow motion to consider activating. Analogous to slowMoActivationDistance in vMeleeAttackControl.")]
public float maxTargetDistance = 3f; // This will be used as slowMoActivationDistance
[Tooltip("Target health threshold below which slow motion might activate (if near).")]
public float lowHealthTh = 10f;
private bool m_hasScaledTime = false;
public float degreeThreshold = 20;
[Tooltip("Time scale to apply during slow motion.")]
public float attackTimeScale = 0.2f;
[Tooltip("Normalized time to start the slow motion window. If < 0, uses 'startDamage'.")]
public float attackTimeScaleStart = -1f;
[Tooltip("Normalized time to end the slow motion window. If < 0, uses 'endDamage'.")]
public float attackTimeScaleEnd = -1f;
[Header("Rotation Settings")]
[Tooltip("If true, the character will attempt to rotate towards the auto-target during this attack state.")]
public bool rotatePlayerTowardsTarget;
private bool m_isRotating;
public float rotationSpeed = 30f;
[Tooltip("The angle (in degrees from player's forward) within which the auto-target must be for rotation to engage. Analogous to rotationActivationAngle in vMeleeAttackControl.")]
public float degreeThreshold = 20f; // This will be used as rotationActivationAngle
// rotationSpeed field is now unused, AutoTargetting.playerRotationSpeed will be used.
// maxTurnTowardDistance field is now unused.
[Header("Debug")]
public bool debug;
// Private state variables
private bool isActive; // Is damage window active
private vIAttackListener mFighter;
private bool isAttacking; // Is this attack state logic considered "attacking"
private bool m_hasScaledTime; // Has slow motion been activated in this instance of the state
private AutoTargetting _autoTargettingInstance;
override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
mFighter = animator.GetComponent<vIAttackListener>();
if (Player.Instance != null)
{
_autoTargettingInstance = Player.Instance.AutoTarget;
}
if (_autoTargettingInstance == null && debug)
{
Debug.LogWarning($"({damageType}) AutoTargetting instance not found via Player.Instance.AutoTarget on {animator.name}. Rotation and target-dependent slow-mo will be limited.");
}
isAttacking = true;
//slowMoActive = false;
isActive = false;
m_hasScaledTime = false;
if (mFighter != null)
mFighter.OnEnableAttack();
if (debug)
Debug.Log("Enter " + damageType);
Debug.Log($"({damageType}) OnStateEnter: {animator.name}, Layer: {layerIndex}");
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<vControlAICombat>();
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;
}
if (attackTimeScaleStart < 0f) attackTimeScaleStart = startDamage;
if (attackTimeScaleEnd < 0f) attackTimeScaleEnd = endDamage;
}
override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
if (Player.Instance.ActiveWeaponTrail)
if (Player.Instance.ActiveWeaponTrail) // Existing weapon trail logic
{
Player.Instance.ActiveWeaponTrail.m_colorMultiplier = Color.white +Color.red * damageMultiplier;
Player.Instance.ActiveWeaponTrail.m_colorMultiplier = Color.white + Color.red * damageMultiplier;
}
float nTime = stateInfo.normalizedTime % 1;
float currentNormalizedTime = stateInfo.normalizedTime % 1;
if (currentNormalizedTime == 0 && stateInfo.normalizedTime > 0.5f) currentNormalizedTime = 1f;
LerpRotation();
// --- ROTATION LOGIC ---
AttemptRotationTowardsAutoTarget(animator);
if (nTime >= startDamage && nTime <= endDamage && !isActive)
// --- SLOW MOTION LOGIC ---
if (useAttackTimeScale)
{
if (debug)
Debug.Log(animator.name + " attack " + damageType + " enable damage in " +
System.Math.Round(stateInfo.normalizedTime % 1, 2));
UpdateSlowMotion(animator, stateInfo, currentNormalizedTime);
}
// --- DAMAGE WINDOW LOGIC ---
if (!isActive && currentNormalizedTime >= startDamage && currentNormalizedTime <= endDamage)
{
if (debug) Debug.Log($"({damageType}) Enable Damage: normTime={currentNormalizedTime:F2} (Start:{startDamage:F2}, End:{endDamage:F2})");
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)
else if (isActive && currentNormalizedTime > endDamage)
{
if (debug)
Debug.Log(animator.name + " attack " + damageType + " disable damage in " +
System.Math.Round(stateInfo.normalizedTime % 1, 2));
if (debug) Debug.Log($"({damageType}) Disable Damage: normTime={currentNormalizedTime:F2} > {endDamage:F2}");
isActive = false;
ActiveDamage(animator, false);
if (m_hasScaledTime)
// Note: Original bMeleeAttackControl reset m_hasScaledTime here.
// vMeleeAttackControl resets it if currentNormalizedTime > attackTimeScaleEnd.
// TimeController handles restoring time, so m_hasScaledTime is more about preventing re-triggering within one state.
// It's reset in UpdateSlowMotion or OnStateExit.
}
// --- ATTACK STATE AND TRIGGER RESET LOGIC ---
if (isAttacking)
{
if (currentNormalizedTime > endDamage)
{
//Time.timeScale = 1f;
m_hasScaledTime = false;
if (mFighter != null) mFighter.OnDisableAttack();
isAttacking = false;
if (debug) Debug.Log($"({damageType}) OnDisableAttack called: normTime={currentNormalizedTime:F2}");
}
// Original bMelee had: "if (nTime > .1f && nTime < resetTriggerBeforeTime && isAttacking)"
// vMelee has: "else if (resetAttackTrigger && currentNormalizedTime >= resetTriggerBeforeTime)"
// Adopting vMelee's more standard approach for early reset:
else if (resetAttackTrigger && currentNormalizedTime >= resetTriggerBeforeTime)
{
if (mFighter != null) mFighter.ResetAttackTriggers();
if (debug) Debug.Log($"({damageType}) ResetAttackTriggers called: normTime={currentNormalizedTime:F2}");
// To prevent multiple calls, ideally ResetAttackTriggers is idempotent or use a flag.
// For now, matching vMelee's potential for multiple calls if time hovers.
}
}
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 (debug) Debug.Log($"({damageType}) OnStateExit: {animator.name}");
if (isActive)
{
isActive = false;
ActiveDamage(animator, false);
if (debug) Debug.Log($"({damageType}) Damage disabled on StateExit.");
}
if (isAttacking)
if (isAttacking && mFighter != null)
{
isAttacking = false;
if (mFighter != null)
mFighter.OnDisableAttack();
mFighter.OnDisableAttack();
if (debug) Debug.Log($"({damageType}) OnDisableAttack called on StateExit (fallback).");
}
isAttacking = false;
m_hasScaledTime = false; // Reset slow motion flag
if (mFighter != null && resetAttackTrigger) // Final reset if configured
{
mFighter.ResetAttackTriggers();
if (debug) Debug.Log($"({damageType}) ResetAttackTriggers called on StateExit due to resetAttackTrigger flag.");
}
}
private void AttemptRotationTowardsAutoTarget(Animator animator)
{
if (!rotatePlayerTowardsTarget || _autoTargettingInstance == null || _autoTargettingInstance.CurrentTarget == null)
{
return;
}
if (mFighter != null && resetAttackTrigger)
mFighter.ResetAttackTriggers();
// Using bMeleeAttackControl's degreeThreshold as the activation angle
if (_autoTargettingInstance.IsTargetInAngle(animator.transform, _autoTargettingInstance.CurrentTarget, degreeThreshold))
{
// AutoTargetting.playerRotationSpeed will be used internally by this call
_autoTargettingInstance.ExecuteRotationTowardsCurrentTarget(Time.deltaTime);
}
}
if (debug) Debug.Log(animator.name + " attack " + damageType + " stateExit");
private void UpdateSlowMotion(Animator animator, AnimatorStateInfo stateInfo, float currentNormalizedTime)
{
// This method is called only if useAttackTimeScale (field) is true.
if (_autoTargettingInstance == null || TimeController.Instance == null) return;
if (!m_hasScaledTime)
{
if (currentNormalizedTime >= attackTimeScaleStart && currentNormalizedTime <= attackTimeScaleEnd)
{
bool triggerSlowMo = false;
if (_autoTargettingInstance.CurrentTarget != null)
{
// Use bMeleeAttackControl's maxTargetDistance as the activation distance
float distSqr = (_autoTargettingInstance.CurrentTarget.transform.position - animator.transform.position).sqrMagnitude;
bool targetNear = distSqr <= maxTargetDistance * maxTargetDistance;
if (targetNear)
{
// Mimicking vMeleeAttackControl's effective logic:
// If useAttackTimeScale (field) is true (which it is to get here) and target is near, then trigger.
// The lowHealthTh can act as an additional, prioritized condition if desired,
// but with current structure, `this.useAttackTimeScale` being true makes the second part of OR true.
float currentTargetHealth = _autoTargettingInstance.GetCurrentTargetHealth();
bool targetHealthLow = currentTargetHealth > 0f && currentTargetHealth < lowHealthTh;
if (targetHealthLow) // Prioritize if health is low and near
{
triggerSlowMo = true;
}
// else if (this.useAttackTimeScale) // This refers to the field, which is true if we are in this function.
// So if target is Near, this path will be taken if not already low health.
// Simplified: if targetNear, triggerSlowMo = true because this.useAttackTimeScale is already true.
// The following `else if` is essentially `else if (true)`
else
{
triggerSlowMo = true; // General case: near and useAttackTimeScale is on
}
}
}
// else: No current target, so no target-dependent slow motion.
if (triggerSlowMo)
{
// Use vMeleeAttackControl's duration calculation for SetTimeScaleForSec
float slowMoEffectDuration = (attackTimeScaleEnd - currentNormalizedTime) * stateInfo.length;
if (slowMoEffectDuration > 0.01f) // Ensure a meaningful duration
{
// The 'true' for forceUnique in bMelee's original TimeController call.
// Assuming TimeController.Instance.SetTimeScaleForSec now handles this or has an overload.
// If SetTimeScaleForSec(scale, duration, bool forceUnique) exists:
// TimeController.Instance.SetTimeScaleForSec(attackTimeScale, slowMoEffectDuration, true);
// If not, use the existing TimeController method. For now, assuming vMelee's version:
TimeController.Instance.SetTimeScaleForSec(attackTimeScale, slowMoEffectDuration);
if (debug) Debug.Log($"({damageType}) Slow-mo ACTIVATED. Target: {_autoTargettingInstance.CurrentTarget?.name ?? "N/A"}. Duration: {slowMoEffectDuration:F2}s. NormTime: {currentNormalizedTime:F2}");
}
else if (debug) Debug.Log($"({damageType}) Slow-mo trigger met, but calculated duration too short ({slowMoEffectDuration:F2}s). NormTime: {currentNormalizedTime:F2}");
m_hasScaledTime = true;
}
}
}
else if (currentNormalizedTime > attackTimeScaleEnd && m_hasScaledTime) // If slow-mo was active and window has passed
{
m_hasScaledTime = false;
if (debug) Debug.Log($"({damageType}) Slow-mo window ended (normTime={currentNormalizedTime:F2}). m_hasScaledTime reset.");
// TimeController.Instance is responsible for restoring time scale.
}
}
void ActiveDamage(Animator animator, bool value)
{
var meleeManager = animator.GetComponent<vMeleeManager>();
if (meleeManager)
{
meleeManager.SetActiveAttack(bodyParts, meleeAttackType, value, damageMultiplier, recoilID, reactionID,
ignoreDefense, activeRagdoll, senselessTime, damageType);
}
else if(debug)
{
Debug.LogWarning($"({damageType}) vMeleeManager not found on {animator.name}. Cannot activate/deactivate damage.");
}
}
}
}