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 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 onHitFireball; // Start is called before the first frame update private void Awake() { tpInput = GetComponent(); 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(); shieldCollisionController = shield.effectObject.GetComponentInChildren(); mpush.effectObject.SetActive(false); mpush.del = MagicPushAttack; if (flameThrowe.effectObject) { flameThrowe.effectObject.GetComponent().Stop(); var ps = flameThrowe.effectObject.GetComponentsInChildren(); 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(); 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(); } } private void OnEnable() { canPlayNoFaithClip = true; canPlayCantDoClip = true; } 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(); 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(); 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(); //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().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(); 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 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 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 } } } } } }