Files
beyond/Assets/Scripts/Characters/Skills/MagicAttacks.cs
2025-01-17 04:23:45 +01:00

503 lines
19 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Invector;
using Invector.vCharacterController;
using Invector.vCharacterController.vActions;
using Sirenix.OdinInspector;
using UnityEditor;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.VFX;
using static Invector.vObjectDamage;
using DG.Tweening;
namespace Beyond
{
[RequireComponent(typeof(Animator))]
[RequireComponent(typeof(vGenericAnimation))]
public class MagicAttacks : MonoBehaviour
{
public delegate void ActionDelegate();
[Serializable]
public class EffectDesc
{
public string name = "MagicPush";
public string secondaryName = "MagicPush Scroll";
public float delay = 0f;
public float startTime = 0f;
public float endTime = 0f;
public GameObject effectObject;
public string animClipName = "MagicPush";
public ActionDelegate del;
}
[SerializeField] private bEquipArea powersArea;
public List<EffectDesc> m_effects;
public EffectDesc selectedEffect;
public enum EffectType
{ MAGIC_PUSH, FLAME_THROWER, SCAN, NOONE, FIREBALL, SHIELD, SILENT_PEEK };
public EffectType m_selectedType = EffectType.NOONE;
public string currentSelectedSpellName = "";
private int currentSpellFaithCost = int.MaxValue;
private Coroutine lastPushRoutine = null;
private ParticleSystem flame;
private BoxCollider flameDamager;
private bLockOn lockOn;
private const float fireballAimerThreshold = -1.0f;
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 ShieldEffectController shieldEffectController;
private ShieldCollisionController shieldCollisionController;
private bool canPlayNoFaithClip = true;
private bool canPlayCantDoClip = true;
public UnityAction<Collider> onHitFireball;
// Start is called before the first frame update
private void Awake()
{
tpInput = GetComponent<bThirdPersonInput>();
EffectDesc mpush = m_effects[(int)EffectType.MAGIC_PUSH];
EffectDesc flameThrowe = m_effects[(int)EffectType.FLAME_THROWER];
EffectDesc scan = m_effects[(int)EffectType.SCAN];
EffectDesc fireball = m_effects[(int)EffectType.FIREBALL];
shield = m_effects[(int)EffectType.SHIELD];
silentPeek = m_effects[(int)EffectType.SILENT_PEEK];
shieldEffectController = shield.effectObject.GetComponent<ShieldEffectController>();
shieldCollisionController = shield.effectObject.GetComponentInChildren<ShieldCollisionController>();
mpush.effectObject.SetActive(false);
mpush.del = MagicPushAttack;
if (flameThrowe.effectObject)
{
flameThrowe.effectObject.GetComponent<ParticleSystem>().Stop();
var ps = flameThrowe.effectObject.GetComponentsInChildren<ParticleSystem>();
foreach (var p in ps)
{
p.Stop();
}
}
flameThrowe.del = FlameThrowerAttack;
scan.del = Scan;
if (scan.effectObject)
{
scan.effectObject.SetActive(false);
}
fireball.del = Fireball;
if (fireball.effectObject)
{
fireball.effectObject.SetActive(false);
}
shield.del = Shield;
if (shield.effectObject)
{
shield.effectObject.SetActive(false);
}
flame = flameThrowe.effectObject.GetComponent<ParticleSystem>();
flameDamager = flameThrowe.effectObject.GetComponentInChildren<BoxCollider>();
lockOn = GetComponent<bLockOn>();
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();
}
}
private void OnEnable()
{
canPlayNoFaithClip = true;
canPlayCantDoClip = true;
}
[Button]
private void OnSilentPeek()
{
StopCoroutine(SilentPeekCoroutine());
StartCoroutine(SilentPeekCoroutine());
}
private IEnumerator SilentPeekCoroutine()
{
EffectDesc peek = m_effects[(int)EffectType.SILENT_PEEK];
yield return new WaitForSeconds(peek.startTime);
if (SilentPeekController.instance.IsActive())
{
SilentPeekController.instance.SetActive(false);
}
else
{
if (peek.effectObject != null)
{
peek.effectObject.SetActive(false);
peek.effectObject.SetActive(true);
yield return new WaitForSeconds(peek.delay);
}
SilentPeekController.instance.SetActive(true, powersArea.equipSlots[equipAreaSelectedIndex].item);
}
yield return null;
}
public void MagicPushAttack()
{
if (lastPushRoutine != null)
{
StopCoroutine(lastPushRoutine);
}
lastPushRoutine = StartCoroutine(MagicPushCoroutine());
}
private IEnumerator MagicPushCoroutine()
{
EffectDesc mpush = m_effects[(int)EffectType.MAGIC_PUSH];
yield return new WaitForSeconds(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);
mpush.effectObject.SetActive(false);
yield return null;
}
public void FlameThrowerAttack()
{
StartCoroutine(FlameThrowerhCoroutine());
}
private IEnumerator FlameThrowerhCoroutine()
{
EffectDesc flameThrowe = m_effects[(int)EffectType.FLAME_THROWER];
yield return new WaitForSeconds(flameThrowe.startTime);
flameDamager.enabled = true;
flame.Play();
yield return new WaitForSeconds(flameThrowe.endTime);
flame.Stop();
yield return new WaitForSeconds(flameThrowe.delay);
flameDamager.enabled = false;
yield return null;
}
[Button]
public void Scan()
{
StartCoroutine(ScanCoroutine());
}
private IEnumerator ScanCoroutine()
{
EffectDesc scan = m_effects[(int)EffectType.SCAN];
yield return new WaitForSeconds(scan.startTime);
float time = scan.startTime - scan.delay;
float maxRange = 50f;
float speed = maxRange / (scan.endTime - scan.startTime);
int mask = 1 << LayerMask.NameToLayer("Triggers") | 1 << LayerMask.NameToLayer("HiddenObject");
if (scan.effectObject)
{
scan.effectObject.SetActive(true);
VisualEffect effect = scan.effectObject.GetComponent<VisualEffect>();
effect.Play();
}
while (time < scan.endTime - scan.delay)
{
Shader.SetGlobalFloat("_WaveTime", speed * (time - scan.startTime));
time += Time.deltaTime;
yield return null;
}
Shader.SetGlobalFloat("_WaveTime", 0f);
var colliders = Physics.OverlapSphere(transform.position, maxRange, mask);
foreach (var c in colliders)
{
var h = c.gameObject.GetComponent<IScannable>();
if (h != null)
h.OnScanned();
}
scan.effectObject.SetActive(false);
yield return null;
}
public void Fireball()
{
StartCoroutine(FireballCoroutine());
}
private IEnumerator FireballCoroutine()
{
EffectDesc fireball = m_effects[(int)EffectType.FIREBALL];
yield return new WaitForSeconds(fireball.startTime);
var fireballClone = Instantiate(fireball.effectObject, fireball.effectObject.transform.position, fireball.effectObject.transform.rotation);
fireballClone.SetActive(true);
RFX4_PhysicsMotion fireballMotionController = fireballClone.GetComponentInChildren<RFX4_PhysicsMotion>(); //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)
{
fireballClone.GetComponentInChildren<vObjectDamage>().onHit.AddListener(onHitFireball);
}
TryToAimToNearbyEnemy(fireballClone, lockOn);
Destroy(fireballClone, 10f);
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<CapsuleCollider>();
StartCoroutine(EnableBrieflyFireballDamagerCoroutine(collider));
}
private void TryToAimToNearbyEnemy(GameObject fireballClone, bLockOn lockOn)
{
if (lockOn.isLockingOn && lockOn.currentTarget != null)
{
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;
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;
}
}
}
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;
Vector3 forward = transform.forward;//(fireballClone.transform.TransformDirection(Vector3.forward)).normalized;
Vector3 toOther = (targetPosition - transform.position).normalized;
if (Vector3.Dot(forward, toOther) > fireballAimerThreshold)
{
toOther.y = 0f;
transform.rotation = Quaternion.LookRotation(toOther);
return;
}
}
}
private IEnumerator EnableBrieflyFireballDamagerCoroutine(CapsuleCollider collider)
{
collider.enabled = true;
yield return new WaitForSeconds(fireballDamagerDuration);
collider.enabled = false;
}
public void Shield()
{
StopCoroutine(ShieldCoroutine());
StartCoroutine(ShieldCoroutine());
}
private IEnumerator ShieldCoroutine()
{
shieldAnimationIsActive = true;
yield return new WaitForSeconds(shield.startTime);
shieldEffectIsActive = true; //this makes the player immune to dmg
shieldEffectController.InitializeEffect();
shield.effectObject.SetActive(true);
shieldCollisionController.shieldCollider.enabled = true;
yield return new WaitForSeconds(shield.endTime);
shieldEffectController.DisableEffect();
yield return new WaitForSeconds(shield.delay / 2f);
shieldEffectIsActive = false;
shieldCollisionController.shieldCollider.enabled = false;
yield return new WaitForSeconds(shield.delay / 2f);
shield.effectObject.SetActive(false);
shieldAnimationIsActive = false;
}
private void DisableShield()
{
shieldEffectIsActive = false;
shieldCollisionController.shieldCollider.enabled = false;
shieldAnimationIsActive = false;
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;
[Tooltip("Where in the end of the animation will trigger the event OnEndAnimation")]
public float animationEnd = 0.8f;
public UnityEvent OnPlayAnimation;
public UnityEvent OnEndAnimation;
public bool isPlaying;
protected bool triggerOnce;
protected vThirdPersonInput tpInput;
internal bool shieldEffectIsActive;
private bool shieldAnimationIsActive;
private int equipAreaSelectedIndex = 0;
protected virtual void LateUpdate()
{
TriggerSpellAnimation();
AnimationBehaviour();
}
protected virtual void TriggerSpellAnimation()
{
// condition to trigger the animation
bool playConditions = !isPlaying && !(tpInput.cc.customAction || tpInput.cc.IsAnimatorTag("special") || tpInput.cc.IsAnimatorTag("LockMovement"));
if (actionInput.GetButtonDown() && playConditions)
TryToPlaySpellAnimation();
}
public void TryToPlaySpellAnimation()
{
// EffectDesc selectedEffect = GetCurrentlySelectedPower();
if (shieldAnimationIsActive && selectedEffect == shield) //casting shield on shield shouldnt be possible
{
TryToPlayCantDoThatYetClip();
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();
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();
}
else
powersArea.UseItem(powersArea.equipSlots[equipAreaSelectedIndex]);
}
}
else
{
TryToPlayNoEnoughFaithClip();
}
}
private void TryToPlayCantDoThatYetClip()
{
if (!canPlayCantDoClip)
{
return;
}
canPlayCantDoClip = false;
DOVirtual.DelayedCall(1, () => canPlayCantDoClip = true);
var text = $"Spell is already active";
bItemCollectionDisplay.Instance.FadeText(text, 4, 0.25f);
Player.Instance.PlayICantDoThatYet();
}
private void TryToPlayNoEnoughFaithClip()
{
if (!canPlayNoFaithClip)
{
return;
}
canPlayNoFaithClip = false;
DOVirtual.DelayedCall(1.5f, () => canPlayNoFaithClip = true);
var text = $"Not enough Faith";
bItemCollectionDisplay.Instance.FadeText(text, 4, 0.25f);
Player.Instance.PlayNoFaithClip();
}
public EffectDesc GetCurrentlySelectedPower()
{
// powersArea.equipSlots[index];
if (powersArea.equipSlots[equipAreaSelectedIndex] == null)
{
currentSpellFaithCost = int.MaxValue;
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);
}
public void SelectPowerBasedOnArea(int newIndex)
{
equipAreaSelectedIndex = newIndex;
selectedEffect = GetCurrentlySelectedPower();
}
protected virtual void AnimationBehaviour()
{ // 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 (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
}
}
}
}
}
}