From 247f3e8050fb238767b869c640b648b3f923ffef Mon Sep 17 00:00:00 2001 From: marcin Date: Thu, 15 May 2025 07:47:52 +0200 Subject: [PATCH] magic attacks with autotargetting --- .../Scripts/Characters/Skills/MagicAttacks.cs | 498 +++++++++++------- 1 file changed, 297 insertions(+), 201 deletions(-) diff --git a/Assets/Scripts/Characters/Skills/MagicAttacks.cs b/Assets/Scripts/Characters/Skills/MagicAttacks.cs index 82de8b082..b162a8669 100644 --- a/Assets/Scripts/Characters/Skills/MagicAttacks.cs +++ b/Assets/Scripts/Characters/Skills/MagicAttacks.cs @@ -6,13 +6,14 @@ using Invector; using Invector.vCharacterController; using Invector.vCharacterController.vActions; using Sirenix.OdinInspector; -using UnityEditor; +// using UnityEditor; // Best to remove if not strictly needed for runtime using UnityEngine; using UnityEngine.Events; using UnityEngine.VFX; -using static Invector.vObjectDamage; +// using static Invector.vObjectDamage; // Not used directly, consider removing using DG.Tweening; using Invector.vCharacterController.AI.FSMBehaviour; +using Beyond; // For Player, GameStateManager, AutoTargetting (if Player.Instance.AutoTarget is of this type) namespace Beyond { @@ -50,35 +51,56 @@ namespace Beyond private Coroutine lastPushRoutine = null; private ParticleSystem flame; private BoxCollider flameDamager; - private bLockOn lockOn; - private const float fireballAimerThreshold = -1.0f; + private bLockOn lockOn; // Retained for fallback aiming or non-combat interactions if needed + private const float fireballAimerThreshold = -1.0f; // Used for fallback aiming if no auto-target private const float fireballAimerHeightAdjuster = 0.1f; private const float fireballDamagerDuration = 0.3f; private const float fireballTargetYPositionOffset = 0.75f; private const int spellLayerIndex = 5; private EffectDesc shield; - private EffectDesc silentPeek; //called covert gaze now + private EffectDesc silentPeek; private ShieldEffectController shieldEffectController; private ShieldCollisionController shieldCollisionController; private bool canPlayNoFaithClip = true; private bool canPlayCantDoClip = true; + + // NEW: AutoTargetting fields + private AutoTargetting _autoTargettingInstance; + [BoxGroup("Auto targetting")] + [Tooltip("Enable to use AutoTargetting for player rotation and spell aiming.")] + public bool enableAutoTargetIntegration = true; + [BoxGroup("Auto targetting")] + [Tooltip("Max distance for player to turn towards an auto-target during spell casting.")] public float maxTurnTowardDistance = 10f; [BoxGroup("Auto targetting")] + [Tooltip("Rotation speed when turning towards an auto-target.")] public float rotationSpeed = 500f; [BoxGroup("Auto targetting")] - public float degreeThreshold = 100; - - - + [Tooltip("Angle threshold within which the player will rotate towards an auto-target.")] + public float degreeThreshold = 100f; public UnityAction onHitFireball; - // Start is called before the first frame update private void Awake() { tpInput = GetComponent(); + // NEW: Initialize AutoTargetting instance + if (Player.Instance != null) + { + _autoTargettingInstance = Player.Instance.AutoTarget; // Assuming Player.Instance has an AutoTarget property of type AutoTargetting + if (_autoTargettingInstance == null && enableAutoTargetIntegration) + { + Debug.LogWarning("MagicAttacks: AutoTargetting component not found on Player.Instance.AutoTarget, but enableAutoTargetIntegration is true. Targeting features will be limited."); + } + } + else + { + Debug.LogError("MagicAttacks: Player.Instance is null in Awake. Cannot get AutoTargetting component."); + } + lockOn = GetComponent(); // Keep for potential fallback + EffectDesc mpush = m_effects[(int)EffectType.MAGIC_PUSH]; EffectDesc flameThrowe = m_effects[(int)EffectType.FLAME_THROWER]; EffectDesc scan = m_effects[(int)EffectType.SCAN]; @@ -122,14 +144,12 @@ namespace Beyond flame = flameThrowe.effectObject.GetComponent(); flameDamager = flameThrowe.effectObject.GetComponentInChildren(); - lockOn = GetComponent(); - + silentPeek.del = OnSilentPeek; } private void OnDisable() { - //for example if you enter cutsene, it should be disabled when we are back to the game if (shieldAnimationIsActive) { DisableShield(); @@ -141,6 +161,7 @@ namespace Beyond canPlayNoFaithClip = true; canPlayCantDoClip = true; } + [Button] private void OnSilentPeek() { @@ -166,7 +187,6 @@ namespace Beyond } SilentPeekController.instance.SetActive(true, powersArea.equipSlots[equipAreaSelectedIndex].item); } - yield return null; } @@ -182,14 +202,14 @@ namespace Beyond private IEnumerator MagicPushCoroutine() { EffectDesc mpush = m_effects[(int)EffectType.MAGIC_PUSH]; - yield return new WaitForSeconds(mpush.startTime); + // MODIFIED: Use TurnTowardTargetCoroutine for potential rotation during wind-up + yield return TurnTowardTargetCoroutine(mpush.startTime); mpush.effectObject.SetActive(false); mpush.effectObject.SetActive(true); yield return new WaitForSeconds(mpush.delay); - Debug.Log("Bum!"); - yield return new WaitForSeconds(mpush.endTime); + Debug.Log("Bum!"); // Consider replacing with actual effect logic + yield return new WaitForSeconds(mpush.endTime); // This is endTime after delay, might be confusing. mpush.effectObject.SetActive(false); - yield return null; } @@ -201,15 +221,14 @@ namespace Beyond private IEnumerator FlameThrowerhCoroutine() { EffectDesc flameThrowe = m_effects[(int)EffectType.FLAME_THROWER]; - //yield return new WaitForSeconds(flameThrowe.startTime); + // MODIFIED: TurnTowardTargetCoroutine will handle rotation based on AutoTargetting yield return TurnTowardTargetCoroutine(flameThrowe.startTime); flameDamager.enabled = true; flame.Play(); - yield return new WaitForSeconds(flameThrowe.endTime); + yield return new WaitForSeconds(flameThrowe.endTime); // Duration of flame flame.Stop(); - yield return new WaitForSeconds(flameThrowe.delay); + yield return new WaitForSeconds(flameThrowe.delay); // Cooldown/after-effect flameDamager.enabled = false; - yield return null; } @@ -222,10 +241,11 @@ namespace Beyond private IEnumerator ScanCoroutine() { EffectDesc scan = m_effects[(int)EffectType.SCAN]; - yield return new WaitForSeconds(scan.startTime); - float time = scan.startTime - scan.delay; + // Scan might not need rotation, but if it had a wind-up animation, TurnTowardTargetCoroutine could be used. + yield return new WaitForSeconds(scan.startTime); + float time = scan.startTime - scan.delay; // This calculation seems off if delay is for after effect. Assuming startTime is actual start. float maxRange = 50f; - float speed = maxRange / (scan.endTime - scan.startTime); + float speed = maxRange / (scan.endTime - scan.startTime); // scan.endTime is duration here int mask = 1 << LayerMask.NameToLayer("Triggers") | 1 << LayerMask.NameToLayer("HiddenObject"); if (scan.effectObject) { @@ -233,13 +253,16 @@ namespace Beyond VisualEffect effect = scan.effectObject.GetComponent(); effect.Play(); } - while (time < scan.endTime - scan.delay) + float waveEffectTimer = 0f; + float waveEffectDuration = scan.endTime - scan.startTime; + + while (waveEffectTimer < waveEffectDuration) { - Shader.SetGlobalFloat("_WaveTime", speed * (time - scan.startTime)); - time += Time.deltaTime; + Shader.SetGlobalFloat("_WaveTime", speed * waveEffectTimer); // Use timer relative to effect start + waveEffectTimer += Time.deltaTime; yield return null; } - Shader.SetGlobalFloat("_WaveTime", 0f); + Shader.SetGlobalFloat("_WaveTime", 0f); // Reset shader global var colliders = Physics.OverlapSphere(transform.position, maxRange, mask); foreach (var c in colliders) { @@ -247,8 +270,7 @@ namespace Beyond if (h != null) h.OnScanned(); } - scan.effectObject.SetActive(false); - + if (scan.effectObject) scan.effectObject.SetActive(false); // Deactivate after use yield return null; } @@ -256,90 +278,129 @@ namespace Beyond { StartCoroutine(FireballCoroutine()); } - - private IEnumerator TurnTowardTargetCoroutine(float maxTime) + + // MODIFIED: Centralized coroutine for turning towards target during spell animations + private IEnumerator TurnTowardTargetCoroutine(float maxDuration) { - float time = 0; - while (time < maxTime) + if (!enableAutoTargetIntegration || _autoTargettingInstance == null) { - time += Time.deltaTime; - LerpRotation(); + // If auto-targeting is off or unavailable, just wait for the duration without rotation. + if (maxDuration > 0) yield return new WaitForSeconds(maxDuration); + yield break; + } + + float timeElapsed = 0; + while (timeElapsed < maxDuration) + { + if (_autoTargettingInstance.CurrentTarget != null) + { + vFSMBehaviourController currentTarget = _autoTargettingInstance.CurrentTarget; + Transform playerTransform = transform; // Character's transform + + float distSqr = (currentTarget.transform.position - playerTransform.position).sqrMagnitude; + + // Check distance using MagicAttacks.maxTurnTowardDistance + if (distSqr <= maxTurnTowardDistance * maxTurnTowardDistance) + { + // Check angle using MagicAttacks.degreeThreshold and AutoTargetting's utility + if (_autoTargettingInstance.IsTargetInAngle(playerTransform, currentTarget, degreeThreshold)) + { + Vector3 directionToTarget = currentTarget.transform.position - playerTransform.position; + directionToTarget.y = 0f; // Horizontal rotation only + + if (directionToTarget.sqrMagnitude > 0.0001f) // Ensure there's a direction + { + Quaternion targetRotation = Quaternion.LookRotation(directionToTarget.normalized); + // Use MagicAttacks.rotationSpeed for the rotation + playerTransform.rotation = Quaternion.RotateTowards(playerTransform.rotation, targetRotation, Time.deltaTime * rotationSpeed); + } + } + } + } + timeElapsed += Time.deltaTime; yield return null; } - yield return null; } + } + private IEnumerator FireballCoroutine() { EffectDesc fireball = m_effects[(int)EffectType.FIREBALL]; - //yield return new WaitForSeconds(fireball.startTime); + // MODIFIED: Use new TurnTowardTargetCoroutine yield return TurnTowardTargetCoroutine(fireball.startTime); + var fireballClone = Instantiate(fireball.effectObject, fireball.effectObject.transform.position, fireball.effectObject.transform.rotation); fireballClone.SetActive(true); - RFX4_PhysicsMotion fireballMotionController = fireballClone.GetComponentInChildren(); //could maybe add some reference container to use get component without children - fireballMotionController.CollisionEnter += EnableBrieflyFireballDamager; //have to do it on each instantiate, since it cant be serialized - if (onHitFireball != null) + RFX4_PhysicsMotion fireballMotionController = fireballClone.GetComponentInChildren(); + if (fireballMotionController != null) { - fireballClone.GetComponentInChildren().onHit.AddListener(onHitFireball); + fireballMotionController.CollisionEnter += EnableBrieflyFireballDamager; } - TryToAimToNearbyEnemy(fireballClone, lockOn); - Destroy(fireballClone, 10f); + vObjectDamage fireballDamageComponent = fireballClone.GetComponentInChildren(); + if (fireballDamageComponent != null && onHitFireball != null) + { + fireballDamageComponent.onHit.AddListener(onHitFireball); + } + + // MODIFIED: Use new AimFireball method + AimFireball(fireballClone); + + Destroy(fireballClone, 10f); // Self-destruct after time yield return null; } private void EnableBrieflyFireballDamager(object sender, RFX4_PhysicsMotion.RFX4_CollisionInfo e) { RFX4_PhysicsMotion rFX4_PhysicsMotion = (RFX4_PhysicsMotion)sender; - CapsuleCollider collider = rFX4_PhysicsMotion.GetComponentInChildren(); - StartCoroutine(EnableBrieflyFireballDamagerCoroutine(collider)); + CapsuleCollider collider = rFX4_PhysicsMotion.GetComponentInChildren(); // Assuming damager is a CapsuleCollider + if(collider != null) StartCoroutine(EnableBrieflyFireballDamagerCoroutine(collider)); } - private void TryToAimToNearbyEnemy(GameObject fireballClone, bLockOn lockOn) + // NEW: Refactored fireball aiming logic + private void AimFireball(GameObject fireballClone) { - if (lockOn.isLockingOn && lockOn.currentTarget != null) + Vector3 aimDirection = transform.forward; // Default aim is player's forward + + if (enableAutoTargetIntegration && _autoTargettingInstance != null && _autoTargettingInstance.CurrentTarget != null) + { + vFSMBehaviourController autoTarget = _autoTargettingInstance.CurrentTarget; + Vector3 targetPosition = autoTarget.transform.position; + targetPosition.y += fireballTargetYPositionOffset; // Adjust height for aiming + aimDirection = (targetPosition - fireballClone.transform.position).normalized; + } + else if (lockOn != null && lockOn.isLockingOn && lockOn.currentTarget != null) // Fallback to bLockOn target { Vector3 targetPosition = lockOn.currentTarget.position; - targetPosition.y += fireballTargetYPositionOffset; //because it gets point on the ground - Vector3 forward = (fireballClone.transform.TransformDirection(Vector3.forward)).normalized; - Vector3 toOther = (targetPosition - fireballClone.transform.position).normalized; - //rotate player toword - transform.rotation = Quaternion.LookRotation(toOther); - fireballClone.transform.rotation = Quaternion.LookRotation(new Vector3(toOther.x, toOther.y + fireballAimerHeightAdjuster, toOther.z)); - return; - } - - List closeEnemies = lockOn.GetNearbyTargets(); //is already nicely sorted - - for (int i = 0; i < closeEnemies.Count; i++) - { - Vector3 targetPosition = closeEnemies[i].position; targetPosition.y += fireballTargetYPositionOffset; - Vector3 forward = transform.forward;//(fireballClone.transform.TransformDirection(Vector3.forward)).normalized; - Vector3 toOther = (targetPosition - fireballClone.transform.position).normalized; - if (Vector3.Dot(forward, toOther) > fireballAimerThreshold) - { - fireballClone.transform.rotation = Quaternion.LookRotation(new Vector3(toOther.x, toOther.y + fireballAimerHeightAdjuster, toOther.z)); - //toOther.y = 0f; - //transform.rotation = Quaternion.LookRotation(toOther); - i = closeEnemies.Count; - } + aimDirection = (targetPosition - fireballClone.transform.position).normalized; } - } - - private void TryToTurnTowordsEnemy() - { - List closeEnemies = lockOn.GetNearbyTargets(); //is already nicely sorted - - for (int i = 0; i < closeEnemies.Count; i++) + else if (lockOn != null) // Fallback to nearest enemy in front (from bLockOn) { - Vector3 targetPosition = closeEnemies[i].position; - Vector3 forward = transform.forward;//(fireballClone.transform.TransformDirection(Vector3.forward)).normalized; - Vector3 toOther = (targetPosition - transform.position).normalized; - if (Vector3.Dot(forward, toOther) > fireballAimerThreshold) + List closeEnemies = lockOn.GetNearbyTargets(); + if (closeEnemies.Count > 0) { - toOther.y = 0f; - transform.rotation = Quaternion.LookRotation(toOther); - return; + foreach (var enemyTransform in closeEnemies) // Find first suitable enemy in front + { + Vector3 targetPosition = enemyTransform.position; + targetPosition.y += fireballTargetYPositionOffset; + Vector3 directionToEnemy = (targetPosition - fireballClone.transform.position).normalized; + + // Check if enemy is generally in front of the player (fireball origin) + if (Vector3.Dot(transform.forward, directionToEnemy) > fireballAimerThreshold) + { + aimDirection = directionToEnemy; + break; + } + } } } + + // Apply calculated aim direction to the fireball, adding vertical adjustment + Vector3 finalAimDirection = new Vector3(aimDirection.x, aimDirection.y + fireballAimerHeightAdjuster, aimDirection.z); + if (finalAimDirection.sqrMagnitude > 0.001f) + { + fireballClone.transform.rotation = Quaternion.LookRotation(finalAimDirection.normalized); + } + // If aimDirection is zero (shouldn't happen with defaults), it will use its instantiated rotation. } @@ -352,24 +413,26 @@ namespace Beyond public void Shield() { - StopCoroutine(ShieldCoroutine()); + StopCoroutine(ShieldCoroutine()); // Ensure only one shield coroutine runs StartCoroutine(ShieldCoroutine()); } private IEnumerator ShieldCoroutine() { shieldAnimationIsActive = true; + // Shield typically doesn't need offensive targeting/rotation. + // If there was a wind-up animation where player *should* face an enemy, TurnTowardTargetCoroutine could be used. yield return new WaitForSeconds(shield.startTime); - shieldEffectIsActive = true; //this makes the player immune to dmg + shieldEffectIsActive = true; shieldEffectController.InitializeEffect(); shield.effectObject.SetActive(true); shieldCollisionController.shieldCollider.enabled = true; - yield return new WaitForSeconds(shield.endTime); + yield return new WaitForSeconds(shield.endTime); // Duration shield is active shieldEffectController.DisableEffect(); - yield return new WaitForSeconds(shield.delay / 2f); + yield return new WaitForSeconds(shield.delay / 2f); // Fade out time part 1 shieldEffectIsActive = false; shieldCollisionController.shieldCollider.enabled = false; - yield return new WaitForSeconds(shield.delay / 2f); + yield return new WaitForSeconds(shield.delay / 2f); // Fade out time part 2 shield.effectObject.SetActive(false); shieldAnimationIsActive = false; } @@ -377,31 +440,31 @@ namespace Beyond private void DisableShield() { shieldEffectIsActive = false; - shieldCollisionController.shieldCollider.enabled = false; + if(shieldCollisionController != null && shieldCollisionController.shieldCollider != null) + shieldCollisionController.shieldCollider.enabled = false; shieldAnimationIsActive = false; - shield.effectObject.SetActive(false); + if(shield != null && shield.effectObject != null) + shield.effectObject.SetActive(false); } [Tooltip("Input to trigger the custom animation")] public GenericInput actionInput = new GenericInput("L", "L", "L"); - [Tooltip("Name of the animation clip")] - public string animationClip; - + public string animationClip; // This will be set by selectedEffect.animClipName [Tooltip("Where in the end of the animation will trigger the event OnEndAnimation")] public float animationEnd = 0.8f; - public UnityEvent OnPlayAnimation; + public UnityEvent OnPlayAnimation; // Consider if this is still needed or how it fits public UnityEvent OnEndAnimation; - public bool isPlaying; - protected bool triggerOnce; - protected vThirdPersonInput tpInput; + public bool isPlaying; // Tracks if the spell animation is currently playing + protected bool triggerOnce; // For OnEndAnimation event + protected vThirdPersonInput tpInput; // Renamed from tpInput to avoid conflict with Invector's tpInput if any confusion internal bool shieldEffectIsActive; private bool shieldAnimationIsActive; private int equipAreaSelectedIndex = 0; - protected virtual void LateUpdate() + protected virtual void LateUpdate() // LateUpdate for animation state checks is common { TriggerSpellAnimation(); AnimationBehaviour(); @@ -409,8 +472,8 @@ namespace Beyond protected virtual void TriggerSpellAnimation() { - // condition to trigger the animation - bool playConditions = !isPlaying && !(tpInput.cc.customAction || tpInput.cc.IsAnimatorTag("special") || tpInput.cc.IsAnimatorTag("LockMovement")); + bool playConditions = !isPlaying && tpInput != null && tpInput.cc != null && + !(tpInput.cc.customAction || tpInput.cc.IsAnimatorTag("special") || tpInput.cc.IsAnimatorTag("LockMovement")); if (actionInput.GetButtonDown() && playConditions) TryToPlaySpellAnimation(); @@ -418,174 +481,207 @@ namespace Beyond public void TryToPlaySpellAnimation() { - // EffectDesc selectedEffect = GetCurrentlySelectedPower(); + selectedEffect = GetCurrentlySelectedPower(); // Ensure current selected effect is fetched - if (shieldAnimationIsActive && selectedEffect == shield) //casting shield on shield shouldnt be possible + if (selectedEffect == shield && shieldAnimationIsActive) { TryToPlayCantDoThatYetClip(); return; } + if (Player.Instance == null) // Safeguard + { + Debug.LogError("Player.Instance is null. Cannot cast spell."); + return; + } + if (selectedEffect != null && currentSpellFaithCost <= Player.Instance.GetCurrentFaithValue()) { Player.Instance.UpdateFaithCurrentValue(-currentSpellFaithCost); - animationClip = selectedEffect.animClipName; - tpInput.cc.animator.CrossFadeInFixedTime(animationClip, 0.1f); - TryToTurnTowordsEnemy(); - selectedEffect.del(); + animationClip = selectedEffect.animClipName; // Set animation clip for AnimationBehaviour + + // NEW: Perform initial snap rotation if auto-targeting is enabled + if (enableAutoTargetIntegration) + { + SnapLookTowardsAutoTarget(); + } + // else // OLD logic for turning - can be removed or kept as fallback + // { + // TryToTurnTowordsEnemy(); + // } + + if (tpInput != null && tpInput.cc != null && tpInput.cc.animator != null) + { + tpInput.cc.animator.CrossFadeInFixedTime(animationClip, 0.1f); + OnPlayAnimation.Invoke(); // Invoke OnPlay event + triggerOnce = true; // Allow OnEndAnimation to be called + } + else + { + Debug.LogError("Cannot play spell animation: tpInput or its components are null."); + return; // Don't proceed to call delegate if animation components are missing + } + + selectedEffect.del?.Invoke(); // Call the spell's primary action delegate + if (powersArea.equipSlots[equipAreaSelectedIndex].item.destroyAfterUse) { if (selectedEffect == silentPeek) { - //for silent peek it is handled in it's controller - //if (SilentPeekController.Instance.IsActive()) - // powersArea.UseEquippedItem(); + // Special handling for Silent Peek item destruction (likely in its controller) } else + { powersArea.UseItem(powersArea.equipSlots[equipAreaSelectedIndex]); + } } } - else + else if (selectedEffect != null) // Not enough faith { TryToPlayNoEnoughFaithClip(); } + // If selectedEffect is null, nothing happens (no spell selected/valid) + } + + // NEW: Method for an immediate snap-look towards the auto-target + private void SnapLookTowardsAutoTarget() + { + if (_autoTargettingInstance == null || _autoTargettingInstance.CurrentTarget == null) + { + return; // No auto-target system or no current target + } + + vFSMBehaviourController currentTarget = _autoTargettingInstance.CurrentTarget; + Transform playerTransform = transform; + + // Check distance condition from MagicAttacks settings + float distSqr = (currentTarget.transform.position - playerTransform.position).sqrMagnitude; + if (distSqr > maxTurnTowardDistance * maxTurnTowardDistance) + { + return; // Target is too far for this specific snap-look interaction + } + + // Check angle condition from MagicAttacks settings + if (!_autoTargettingInstance.IsTargetInAngle(playerTransform, currentTarget, degreeThreshold)) + { + return; // Target is not within the desired cone for snap-look + } + + Vector3 directionToTarget = currentTarget.transform.position - playerTransform.position; + directionToTarget.y = 0f; // Horizontal rotation only + + if (directionToTarget.sqrMagnitude > 0.0001f) + { + playerTransform.rotation = Quaternion.LookRotation(directionToTarget.normalized); + } } private void TryToPlayCantDoThatYetClip() { - if (!canPlayCantDoClip) - { - return; - } + if (!canPlayCantDoClip) return; canPlayCantDoClip = false; - DOVirtual.DelayedCall(1, () => canPlayCantDoClip = true); + DOVirtual.DelayedCall(1f, () => canPlayCantDoClip = true); // Reset flag - var text = $"Spell is already active"; - bItemCollectionDisplay.Instance.FadeText(text, 4, 0.25f); - Player.Instance.PlayICantDoThatYet(); + var text = "Spell is already active"; // Example message + if (bItemCollectionDisplay.Instance != null) + bItemCollectionDisplay.Instance.FadeText(text, 4, 0.25f); + if (Player.Instance != null) + Player.Instance.PlayICantDoThatYet(); // Assuming this method exists on Player } private void TryToPlayNoEnoughFaithClip() { - if (!canPlayNoFaithClip) - { - return; - } + if (!canPlayNoFaithClip) return; canPlayNoFaithClip = false; - DOVirtual.DelayedCall(1.5f, () => canPlayNoFaithClip = true); + DOVirtual.DelayedCall(1.5f, () => canPlayNoFaithClip = true); // Reset flag - var text = $"Not enough Faith"; - bItemCollectionDisplay.Instance.FadeText(text, 4, 0.25f); - Player.Instance.PlayNoFaithClip(); + var text = "Not enough Faith"; + if (bItemCollectionDisplay.Instance != null) + bItemCollectionDisplay.Instance.FadeText(text, 4, 0.25f); + if (Player.Instance != null) + Player.Instance.PlayNoFaithClip(); // Assuming this method exists on Player } public EffectDesc GetCurrentlySelectedPower() { - // powersArea.equipSlots[index]; - - if (powersArea.equipSlots[equipAreaSelectedIndex] == null) + if (powersArea == null || equipAreaSelectedIndex < 0 || equipAreaSelectedIndex >= powersArea.equipSlots.Count || + powersArea.equipSlots[equipAreaSelectedIndex] == null || powersArea.equipSlots[equipAreaSelectedIndex].item == null) { currentSpellFaithCost = int.MaxValue; + currentSelectedSpellName = ""; return null; } - bItem selectedSpell = powersArea.equipSlots[equipAreaSelectedIndex].item; - string itemName = selectedSpell.name; - currentSpellFaithCost = selectedSpell.GetItemAttribute(bItemAttributes.Faith).value; - return m_effects.Find(item => item.name == itemName || item.secondaryName == itemName); + + bItem selectedSpellItem = powersArea.equipSlots[equipAreaSelectedIndex].item; + currentSelectedSpellName = selectedSpellItem.name; // Store for display or debugging + currentSpellFaithCost = selectedSpellItem.GetItemAttribute(bItemAttributes.Faith).value; + + return m_effects.Find(effect => effect.name == selectedSpellItem.name || effect.secondaryName == selectedSpellItem.name); } public void SelectPowerBasedOnArea(int newIndex) { equipAreaSelectedIndex = newIndex; - selectedEffect = GetCurrentlySelectedPower(); + selectedEffect = GetCurrentlySelectedPower(); // Update current effect based on selection } + // OLD rotation methods - can be removed or commented out if new system is preferred + /* private void LerpRotation() { - - 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; + // ... original LerpRotation code using GetNearestEnemy ... } 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; + // ... original IsEnemyInAngleRange code ... } 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; + // ... original GetNearestEnemy code ... } - + private void TryToTurnTowordsEnemy() // Replaced by SnapLookTowardsAutoTarget + { + // ... original TryToTurnTowordsEnemy code ... + } + */ protected virtual void AnimationBehaviour() + { + if (tpInput == null || tpInput.cc == null || tpInput.cc.animator == null || string.IsNullOrEmpty(animationClip)) + { + isPlaying = false; + return; + } + + // isPlaying should reflect if the *specific spell animation* is active. + isPlaying = tpInput.cc.animator.GetCurrentAnimatorStateInfo(spellLayerIndex).IsName(animationClip) || + tpInput.cc.animator.GetNextAnimatorStateInfo(spellLayerIndex).IsName(animationClip); - { // know if the animation is playing or not - // isPlaying = spellLayerInfo.IsName(animationClip); - - isPlaying = tpInput.cc.animator.GetCurrentAnimatorStateInfo(spellLayerIndex).IsName(animationClip); - // isPlaying = tpInput.cc.baseLayerInfo.IsName(animationClip); if (isPlaying) { - // detected the end of the animation clip to trigger the OnEndAnimation Event - if (tpInput.cc.animator.GetCurrentAnimatorStateInfo(spellLayerIndex).normalizedTime >= animationEnd) + if (tpInput.cc.animator.GetCurrentAnimatorStateInfo(spellLayerIndex).IsName(animationClip) && + tpInput.cc.animator.GetCurrentAnimatorStateInfo(spellLayerIndex).normalizedTime >= animationEnd) { if (triggerOnce) { - triggerOnce = false; // reset the bool so we can call the event again - OnEndAnimation.Invoke(); // call the OnEnd Event at the end of the animation + triggerOnce = false; + OnEndAnimation.Invoke(); } } } + else + { + // If not playing the specific animation clip, ensure triggerOnce is reset + // This handles cases where animation might be interrupted before reaching animationEnd + if (triggerOnce) + { + triggerOnce = false; + // Optionally, invoke OnEndAnimation if it should always fire on exiting the state, + // but current logic only fires it if normalizedTime >= animationEnd. + } + } } } } \ No newline at end of file