magic attacks with autotargetting

This commit is contained in:
2025-05-15 07:47:52 +02:00
parent 86d35d8eaa
commit 247f3e8050

View File

@@ -6,13 +6,14 @@ using Invector;
using Invector.vCharacterController; using Invector.vCharacterController;
using Invector.vCharacterController.vActions; using Invector.vCharacterController.vActions;
using Sirenix.OdinInspector; using Sirenix.OdinInspector;
using UnityEditor; // using UnityEditor; // Best to remove if not strictly needed for runtime
using UnityEngine; using UnityEngine;
using UnityEngine.Events; using UnityEngine.Events;
using UnityEngine.VFX; using UnityEngine.VFX;
using static Invector.vObjectDamage; // using static Invector.vObjectDamage; // Not used directly, consider removing
using DG.Tweening; using DG.Tweening;
using Invector.vCharacterController.AI.FSMBehaviour; using Invector.vCharacterController.AI.FSMBehaviour;
using Beyond; // For Player, GameStateManager, AutoTargetting (if Player.Instance.AutoTarget is of this type)
namespace Beyond namespace Beyond
{ {
@@ -50,35 +51,56 @@ namespace Beyond
private Coroutine lastPushRoutine = null; private Coroutine lastPushRoutine = null;
private ParticleSystem flame; private ParticleSystem flame;
private BoxCollider flameDamager; private BoxCollider flameDamager;
private bLockOn lockOn; private bLockOn lockOn; // Retained for fallback aiming or non-combat interactions if needed
private const float fireballAimerThreshold = -1.0f; private const float fireballAimerThreshold = -1.0f; // Used for fallback aiming if no auto-target
private const float fireballAimerHeightAdjuster = 0.1f; private const float fireballAimerHeightAdjuster = 0.1f;
private const float fireballDamagerDuration = 0.3f; private const float fireballDamagerDuration = 0.3f;
private const float fireballTargetYPositionOffset = 0.75f; private const float fireballTargetYPositionOffset = 0.75f;
private const int spellLayerIndex = 5; private const int spellLayerIndex = 5;
private EffectDesc shield; private EffectDesc shield;
private EffectDesc silentPeek; //called covert gaze now private EffectDesc silentPeek;
private ShieldEffectController shieldEffectController; private ShieldEffectController shieldEffectController;
private ShieldCollisionController shieldCollisionController; private ShieldCollisionController shieldCollisionController;
private bool canPlayNoFaithClip = true; private bool canPlayNoFaithClip = true;
private bool canPlayCantDoClip = true; private bool canPlayCantDoClip = true;
// NEW: AutoTargetting fields
private AutoTargetting _autoTargettingInstance;
[BoxGroup("Auto targetting")] [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; public float maxTurnTowardDistance = 10f;
[BoxGroup("Auto targetting")] [BoxGroup("Auto targetting")]
[Tooltip("Rotation speed when turning towards an auto-target.")]
public float rotationSpeed = 500f; public float rotationSpeed = 500f;
[BoxGroup("Auto targetting")] [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<Collider> onHitFireball; public UnityAction<Collider> onHitFireball;
// Start is called before the first frame update
private void Awake() private void Awake()
{ {
tpInput = GetComponent<bThirdPersonInput>(); tpInput = GetComponent<bThirdPersonInput>();
// 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<bLockOn>(); // Keep for potential fallback
EffectDesc mpush = m_effects[(int)EffectType.MAGIC_PUSH]; EffectDesc mpush = m_effects[(int)EffectType.MAGIC_PUSH];
EffectDesc flameThrowe = m_effects[(int)EffectType.FLAME_THROWER]; EffectDesc flameThrowe = m_effects[(int)EffectType.FLAME_THROWER];
EffectDesc scan = m_effects[(int)EffectType.SCAN]; EffectDesc scan = m_effects[(int)EffectType.SCAN];
@@ -122,14 +144,12 @@ namespace Beyond
flame = flameThrowe.effectObject.GetComponent<ParticleSystem>(); flame = flameThrowe.effectObject.GetComponent<ParticleSystem>();
flameDamager = flameThrowe.effectObject.GetComponentInChildren<BoxCollider>(); flameDamager = flameThrowe.effectObject.GetComponentInChildren<BoxCollider>();
lockOn = GetComponent<bLockOn>();
silentPeek.del = OnSilentPeek; silentPeek.del = OnSilentPeek;
} }
private void OnDisable() private void OnDisable()
{ {
//for example if you enter cutsene, it should be disabled when we are back to the game
if (shieldAnimationIsActive) if (shieldAnimationIsActive)
{ {
DisableShield(); DisableShield();
@@ -141,6 +161,7 @@ namespace Beyond
canPlayNoFaithClip = true; canPlayNoFaithClip = true;
canPlayCantDoClip = true; canPlayCantDoClip = true;
} }
[Button] [Button]
private void OnSilentPeek() private void OnSilentPeek()
{ {
@@ -166,7 +187,6 @@ namespace Beyond
} }
SilentPeekController.instance.SetActive(true, powersArea.equipSlots[equipAreaSelectedIndex].item); SilentPeekController.instance.SetActive(true, powersArea.equipSlots[equipAreaSelectedIndex].item);
} }
yield return null; yield return null;
} }
@@ -182,14 +202,14 @@ namespace Beyond
private IEnumerator MagicPushCoroutine() private IEnumerator MagicPushCoroutine()
{ {
EffectDesc mpush = m_effects[(int)EffectType.MAGIC_PUSH]; 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(false);
mpush.effectObject.SetActive(true); mpush.effectObject.SetActive(true);
yield return new WaitForSeconds(mpush.delay); yield return new WaitForSeconds(mpush.delay);
Debug.Log("Bum!"); Debug.Log("Bum!"); // Consider replacing with actual effect logic
yield return new WaitForSeconds(mpush.endTime); yield return new WaitForSeconds(mpush.endTime); // This is endTime after delay, might be confusing.
mpush.effectObject.SetActive(false); mpush.effectObject.SetActive(false);
yield return null; yield return null;
} }
@@ -201,15 +221,14 @@ namespace Beyond
private IEnumerator FlameThrowerhCoroutine() private IEnumerator FlameThrowerhCoroutine()
{ {
EffectDesc flameThrowe = m_effects[(int)EffectType.FLAME_THROWER]; 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); yield return TurnTowardTargetCoroutine(flameThrowe.startTime);
flameDamager.enabled = true; flameDamager.enabled = true;
flame.Play(); flame.Play();
yield return new WaitForSeconds(flameThrowe.endTime); yield return new WaitForSeconds(flameThrowe.endTime); // Duration of flame
flame.Stop(); flame.Stop();
yield return new WaitForSeconds(flameThrowe.delay); yield return new WaitForSeconds(flameThrowe.delay); // Cooldown/after-effect
flameDamager.enabled = false; flameDamager.enabled = false;
yield return null; yield return null;
} }
@@ -222,10 +241,11 @@ namespace Beyond
private IEnumerator ScanCoroutine() private IEnumerator ScanCoroutine()
{ {
EffectDesc scan = m_effects[(int)EffectType.SCAN]; EffectDesc scan = m_effects[(int)EffectType.SCAN];
yield return new WaitForSeconds(scan.startTime); // Scan might not need rotation, but if it had a wind-up animation, TurnTowardTargetCoroutine could be used.
float time = scan.startTime - scan.delay; 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 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"); int mask = 1 << LayerMask.NameToLayer("Triggers") | 1 << LayerMask.NameToLayer("HiddenObject");
if (scan.effectObject) if (scan.effectObject)
{ {
@@ -233,13 +253,16 @@ namespace Beyond
VisualEffect effect = scan.effectObject.GetComponent<VisualEffect>(); VisualEffect effect = scan.effectObject.GetComponent<VisualEffect>();
effect.Play(); 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)); Shader.SetGlobalFloat("_WaveTime", speed * waveEffectTimer); // Use timer relative to effect start
time += Time.deltaTime; waveEffectTimer += Time.deltaTime;
yield return null; yield return null;
} }
Shader.SetGlobalFloat("_WaveTime", 0f); Shader.SetGlobalFloat("_WaveTime", 0f); // Reset shader global
var colliders = Physics.OverlapSphere(transform.position, maxRange, mask); var colliders = Physics.OverlapSphere(transform.position, maxRange, mask);
foreach (var c in colliders) foreach (var c in colliders)
{ {
@@ -247,8 +270,7 @@ namespace Beyond
if (h != null) if (h != null)
h.OnScanned(); h.OnScanned();
} }
scan.effectObject.SetActive(false); if (scan.effectObject) scan.effectObject.SetActive(false); // Deactivate after use
yield return null; yield return null;
} }
@@ -256,90 +278,129 @@ namespace Beyond
{ {
StartCoroutine(FireballCoroutine()); 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; if (!enableAutoTargetIntegration || _autoTargettingInstance == null)
while (time < maxTime)
{ {
time += Time.deltaTime; // If auto-targeting is off or unavailable, just wait for the duration without rotation.
LerpRotation(); 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;
} }
yield return null; } }
private IEnumerator FireballCoroutine() private IEnumerator FireballCoroutine()
{ {
EffectDesc fireball = m_effects[(int)EffectType.FIREBALL]; EffectDesc fireball = m_effects[(int)EffectType.FIREBALL];
//yield return new WaitForSeconds(fireball.startTime); // MODIFIED: Use new TurnTowardTargetCoroutine
yield return TurnTowardTargetCoroutine(fireball.startTime); yield return TurnTowardTargetCoroutine(fireball.startTime);
var fireballClone = Instantiate(fireball.effectObject, fireball.effectObject.transform.position, fireball.effectObject.transform.rotation); var fireballClone = Instantiate(fireball.effectObject, fireball.effectObject.transform.position, fireball.effectObject.transform.rotation);
fireballClone.SetActive(true); fireballClone.SetActive(true);
RFX4_PhysicsMotion fireballMotionController = fireballClone.GetComponentInChildren<RFX4_PhysicsMotion>(); //could maybe add some reference container to use get component without children RFX4_PhysicsMotion fireballMotionController = fireballClone.GetComponentInChildren<RFX4_PhysicsMotion>();
fireballMotionController.CollisionEnter += EnableBrieflyFireballDamager; //have to do it on each instantiate, since it cant be serialized if (fireballMotionController != null)
if (onHitFireball != null)
{ {
fireballClone.GetComponentInChildren<vObjectDamage>().onHit.AddListener(onHitFireball); fireballMotionController.CollisionEnter += EnableBrieflyFireballDamager;
} }
TryToAimToNearbyEnemy(fireballClone, lockOn); vObjectDamage fireballDamageComponent = fireballClone.GetComponentInChildren<vObjectDamage>();
Destroy(fireballClone, 10f); 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; yield return null;
} }
private void EnableBrieflyFireballDamager(object sender, RFX4_PhysicsMotion.RFX4_CollisionInfo e) private void EnableBrieflyFireballDamager(object sender, RFX4_PhysicsMotion.RFX4_CollisionInfo e)
{ {
RFX4_PhysicsMotion rFX4_PhysicsMotion = (RFX4_PhysicsMotion)sender; RFX4_PhysicsMotion rFX4_PhysicsMotion = (RFX4_PhysicsMotion)sender;
CapsuleCollider collider = rFX4_PhysicsMotion.GetComponentInChildren<CapsuleCollider>(); CapsuleCollider collider = rFX4_PhysicsMotion.GetComponentInChildren<CapsuleCollider>(); // Assuming damager is a CapsuleCollider
StartCoroutine(EnableBrieflyFireballDamagerCoroutine(collider)); 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; 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<Transform> closeEnemies = lockOn.GetNearbyTargets(); //is already nicely sorted
for (int i = 0; i < closeEnemies.Count; i++)
{
Vector3 targetPosition = closeEnemies[i].position;
targetPosition.y += fireballTargetYPositionOffset; targetPosition.y += fireballTargetYPositionOffset;
Vector3 forward = transform.forward;//(fireballClone.transform.TransformDirection(Vector3.forward)).normalized; aimDirection = (targetPosition - fireballClone.transform.position).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;
}
} }
} else if (lockOn != null) // Fallback to nearest enemy in front (from bLockOn)
private void TryToTurnTowordsEnemy()
{
List<Transform> closeEnemies = lockOn.GetNearbyTargets(); //is already nicely sorted
for (int i = 0; i < closeEnemies.Count; i++)
{ {
Vector3 targetPosition = closeEnemies[i].position; List<Transform> closeEnemies = lockOn.GetNearbyTargets();
Vector3 forward = transform.forward;//(fireballClone.transform.TransformDirection(Vector3.forward)).normalized; if (closeEnemies.Count > 0)
Vector3 toOther = (targetPosition - transform.position).normalized;
if (Vector3.Dot(forward, toOther) > fireballAimerThreshold)
{ {
toOther.y = 0f; foreach (var enemyTransform in closeEnemies) // Find first suitable enemy in front
transform.rotation = Quaternion.LookRotation(toOther); {
return; 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() public void Shield()
{ {
StopCoroutine(ShieldCoroutine()); StopCoroutine(ShieldCoroutine()); // Ensure only one shield coroutine runs
StartCoroutine(ShieldCoroutine()); StartCoroutine(ShieldCoroutine());
} }
private IEnumerator ShieldCoroutine() private IEnumerator ShieldCoroutine()
{ {
shieldAnimationIsActive = true; 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); yield return new WaitForSeconds(shield.startTime);
shieldEffectIsActive = true; //this makes the player immune to dmg shieldEffectIsActive = true;
shieldEffectController.InitializeEffect(); shieldEffectController.InitializeEffect();
shield.effectObject.SetActive(true); shield.effectObject.SetActive(true);
shieldCollisionController.shieldCollider.enabled = true; shieldCollisionController.shieldCollider.enabled = true;
yield return new WaitForSeconds(shield.endTime); yield return new WaitForSeconds(shield.endTime); // Duration shield is active
shieldEffectController.DisableEffect(); shieldEffectController.DisableEffect();
yield return new WaitForSeconds(shield.delay / 2f); yield return new WaitForSeconds(shield.delay / 2f); // Fade out time part 1
shieldEffectIsActive = false; shieldEffectIsActive = false;
shieldCollisionController.shieldCollider.enabled = 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); shield.effectObject.SetActive(false);
shieldAnimationIsActive = false; shieldAnimationIsActive = false;
} }
@@ -377,31 +440,31 @@ namespace Beyond
private void DisableShield() private void DisableShield()
{ {
shieldEffectIsActive = false; shieldEffectIsActive = false;
shieldCollisionController.shieldCollider.enabled = false; if(shieldCollisionController != null && shieldCollisionController.shieldCollider != null)
shieldCollisionController.shieldCollider.enabled = false;
shieldAnimationIsActive = false; shieldAnimationIsActive = false;
shield.effectObject.SetActive(false); if(shield != null && shield.effectObject != null)
shield.effectObject.SetActive(false);
} }
[Tooltip("Input to trigger the custom animation")] [Tooltip("Input to trigger the custom animation")]
public GenericInput actionInput = new GenericInput("L", "L", "L"); public GenericInput actionInput = new GenericInput("L", "L", "L");
[Tooltip("Name of the animation clip")] [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")] [Tooltip("Where in the end of the animation will trigger the event OnEndAnimation")]
public float animationEnd = 0.8f; 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 UnityEvent OnEndAnimation;
public bool isPlaying; public bool isPlaying; // Tracks if the spell animation is currently playing
protected bool triggerOnce; protected bool triggerOnce; // For OnEndAnimation event
protected vThirdPersonInput tpInput; protected vThirdPersonInput tpInput; // Renamed from tpInput to avoid conflict with Invector's tpInput if any confusion
internal bool shieldEffectIsActive; internal bool shieldEffectIsActive;
private bool shieldAnimationIsActive; private bool shieldAnimationIsActive;
private int equipAreaSelectedIndex = 0; private int equipAreaSelectedIndex = 0;
protected virtual void LateUpdate() protected virtual void LateUpdate() // LateUpdate for animation state checks is common
{ {
TriggerSpellAnimation(); TriggerSpellAnimation();
AnimationBehaviour(); AnimationBehaviour();
@@ -409,8 +472,8 @@ namespace Beyond
protected virtual void TriggerSpellAnimation() protected virtual void TriggerSpellAnimation()
{ {
// condition to trigger the animation bool playConditions = !isPlaying && tpInput != null && tpInput.cc != null &&
bool playConditions = !isPlaying && !(tpInput.cc.customAction || tpInput.cc.IsAnimatorTag("special") || tpInput.cc.IsAnimatorTag("LockMovement")); !(tpInput.cc.customAction || tpInput.cc.IsAnimatorTag("special") || tpInput.cc.IsAnimatorTag("LockMovement"));
if (actionInput.GetButtonDown() && playConditions) if (actionInput.GetButtonDown() && playConditions)
TryToPlaySpellAnimation(); TryToPlaySpellAnimation();
@@ -418,174 +481,207 @@ namespace Beyond
public void TryToPlaySpellAnimation() 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(); TryToPlayCantDoThatYetClip();
return; return;
} }
if (Player.Instance == null) // Safeguard
{
Debug.LogError("Player.Instance is null. Cannot cast spell.");
return;
}
if (selectedEffect != null && currentSpellFaithCost <= Player.Instance.GetCurrentFaithValue()) if (selectedEffect != null && currentSpellFaithCost <= Player.Instance.GetCurrentFaithValue())
{ {
Player.Instance.UpdateFaithCurrentValue(-currentSpellFaithCost); Player.Instance.UpdateFaithCurrentValue(-currentSpellFaithCost);
animationClip = selectedEffect.animClipName; animationClip = selectedEffect.animClipName; // Set animation clip for AnimationBehaviour
tpInput.cc.animator.CrossFadeInFixedTime(animationClip, 0.1f);
TryToTurnTowordsEnemy(); // NEW: Perform initial snap rotation if auto-targeting is enabled
selectedEffect.del(); 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 (powersArea.equipSlots[equipAreaSelectedIndex].item.destroyAfterUse)
{ {
if (selectedEffect == silentPeek) if (selectedEffect == silentPeek)
{ {
//for silent peek it is handled in it's controller // Special handling for Silent Peek item destruction (likely in its controller)
//if (SilentPeekController.Instance.IsActive())
// powersArea.UseEquippedItem();
} }
else else
{
powersArea.UseItem(powersArea.equipSlots[equipAreaSelectedIndex]); powersArea.UseItem(powersArea.equipSlots[equipAreaSelectedIndex]);
}
} }
} }
else else if (selectedEffect != null) // Not enough faith
{ {
TryToPlayNoEnoughFaithClip(); 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() private void TryToPlayCantDoThatYetClip()
{ {
if (!canPlayCantDoClip) if (!canPlayCantDoClip) return;
{
return;
}
canPlayCantDoClip = false; canPlayCantDoClip = false;
DOVirtual.DelayedCall(1, () => canPlayCantDoClip = true); DOVirtual.DelayedCall(1f, () => canPlayCantDoClip = true); // Reset flag
var text = $"Spell is already active"; var text = "Spell is already active"; // Example message
bItemCollectionDisplay.Instance.FadeText(text, 4, 0.25f); if (bItemCollectionDisplay.Instance != null)
Player.Instance.PlayICantDoThatYet(); bItemCollectionDisplay.Instance.FadeText(text, 4, 0.25f);
if (Player.Instance != null)
Player.Instance.PlayICantDoThatYet(); // Assuming this method exists on Player
} }
private void TryToPlayNoEnoughFaithClip() private void TryToPlayNoEnoughFaithClip()
{ {
if (!canPlayNoFaithClip) if (!canPlayNoFaithClip) return;
{
return;
}
canPlayNoFaithClip = false; canPlayNoFaithClip = false;
DOVirtual.DelayedCall(1.5f, () => canPlayNoFaithClip = true); DOVirtual.DelayedCall(1.5f, () => canPlayNoFaithClip = true); // Reset flag
var text = $"Not enough Faith"; var text = "Not enough Faith";
bItemCollectionDisplay.Instance.FadeText(text, 4, 0.25f); if (bItemCollectionDisplay.Instance != null)
Player.Instance.PlayNoFaithClip(); bItemCollectionDisplay.Instance.FadeText(text, 4, 0.25f);
if (Player.Instance != null)
Player.Instance.PlayNoFaithClip(); // Assuming this method exists on Player
} }
public EffectDesc GetCurrentlySelectedPower() public EffectDesc GetCurrentlySelectedPower()
{ {
// powersArea.equipSlots[index]; if (powersArea == null || equipAreaSelectedIndex < 0 || equipAreaSelectedIndex >= powersArea.equipSlots.Count ||
powersArea.equipSlots[equipAreaSelectedIndex] == null || powersArea.equipSlots[equipAreaSelectedIndex].item == null)
if (powersArea.equipSlots[equipAreaSelectedIndex] == null)
{ {
currentSpellFaithCost = int.MaxValue; currentSpellFaithCost = int.MaxValue;
currentSelectedSpellName = "";
return null; return null;
} }
bItem selectedSpell = powersArea.equipSlots[equipAreaSelectedIndex].item;
string itemName = selectedSpell.name; bItem selectedSpellItem = powersArea.equipSlots[equipAreaSelectedIndex].item;
currentSpellFaithCost = selectedSpell.GetItemAttribute(bItemAttributes.Faith).value; currentSelectedSpellName = selectedSpellItem.name; // Store for display or debugging
return m_effects.Find(item => item.name == itemName || item.secondaryName == itemName); currentSpellFaithCost = selectedSpellItem.GetItemAttribute(bItemAttributes.Faith).value;
return m_effects.Find(effect => effect.name == selectedSpellItem.name || effect.secondaryName == selectedSpellItem.name);
} }
public void SelectPowerBasedOnArea(int newIndex) public void SelectPowerBasedOnArea(int newIndex)
{ {
equipAreaSelectedIndex = 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() private void LerpRotation()
{ {
// ... original LerpRotation code using GetNearestEnemy ...
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) private bool IsEnemyInAngleRange(vFSMBehaviourController ai)
{ {
if (ai == null) // ... original IsEnemyInAngleRange code ...
{
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;
} }
private vFSMBehaviourController GetNearestEnemy(ref float minDist) private vFSMBehaviourController GetNearestEnemy(ref float minDist)
{ {
var controllers = GameStateManager.Instance.GetActiveCombatcontrollers(); // ... original GetNearestEnemy code ...
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;
} }
private void TryToTurnTowordsEnemy() // Replaced by SnapLookTowardsAutoTarget
{
// ... original TryToTurnTowordsEnemy code ...
}
*/
protected virtual void AnimationBehaviour() 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) if (isPlaying)
{ {
// detected the end of the animation clip to trigger the OnEndAnimation Event if (tpInput.cc.animator.GetCurrentAnimatorStateInfo(spellLayerIndex).IsName(animationClip) &&
if (tpInput.cc.animator.GetCurrentAnimatorStateInfo(spellLayerIndex).normalizedTime >= animationEnd) tpInput.cc.animator.GetCurrentAnimatorStateInfo(spellLayerIndex).normalizedTime >= animationEnd)
{ {
if (triggerOnce) if (triggerOnce)
{ {
triggerOnce = false; // reset the bool so we can call the event again triggerOnce = false;
OnEndAnimation.Invoke(); // call the OnEnd Event at the end of the animation 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.
}
}
} }
} }
} }