Newer
Older
TheVengeance-Project-IADE-Unity2D / Assets / Scripts / Player / PlayerController.cs
using MyCollections.DesignPatterns.Visitor;
using MyCollections.Managers;
using MyCollections.SceneManagement;
using MyCollections.Stats;
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.SceneManagement;

public class PlayerController : MonoBehaviour, IHealthSystem, IVisitable
{
    //Variables
    private Rigidbody2D rb;
    private Animator anim;
    private static PlayerController instance;

    public bool flashActive;
    [SerializeField]
    public float flashLength = 0f;
    [SerializeField]
    public float flashCounter = 0f;

    [Header("Stats: ")]
    private BaseStats baseStats;
    public Stats Stats { get; private set; }

    [Header("Attribute Settings: ")]
    [SerializeField] private int health = 100;
    [SerializeField] private int maxHealth = 100;
    [SerializeField] private float speed = 0f;
    [SerializeField] private float runSpeed = 0f;

    [Header("Combat Settings: ")]
    [SerializeField] private LayerMask enemyLayerMask;
    [SerializeField] private bool isAttacking = false;
    [SerializeField] private int attackBaseValue = 20;
    [SerializeField] private int defenseBaseValue = 5;
    [SerializeField] private float attackRange = 2f;
    [SerializeField] private float attackTimer = 0f;
    [SerializeField] private float maxAttackTimer = 1.5f;
    [SerializeField] private float attackForce = 5f;

    [Header("Mana Settings: ")]
    [SerializeField] private float mana = 100;
    [SerializeField] private float maxMana = 100;
    [SerializeField] private float manaIncreaseRate = 0.5f;
    [SerializeField] private float manaDecreaseRate = 0.5f;
    [SerializeField] private float manaTimer = 2f;
    [SerializeField] private float manaTimerMax = 2f;

    [Header("Other Settings: ")]
    [SerializeField] private bool canMove = true;
    [SerializeField] private bool isRuning = false;
    [SerializeField] private Vector2 lastInput;

    private float horizontalInput, verticalInput;
    public bool isTalking = false;
    public bool shield;
    private GameObject interactibleGameObject;

    //Events
    public event Action<int> OnHealthChanged = delegate { };
    public event Action<float> OnManaChanged = delegate { };
    public event Action<int> OnXPChange = delegate { };

    //Properties
    public static PlayerController Instance => instance;
    public bool CanMove
    {
        get => canMove;
        set => canMove = value;
    }

    public int CurrentPlayerAttack
    {
        get => attackBaseValue;
        set => attackBaseValue += value;
    }

    public int Health
    {
        get => health;
        set
        {
            health = Mathf.Clamp(value, 0, maxHealth);  // Clamp health within valid range
            OnHealthChanged?.Invoke(health);      // Trigger the event when health changes
        }
    }

    public float Mana
    {
        get => mana;
        set
        {
            mana = Mathf.Clamp(value, 0, maxMana);
            OnManaChanged?.Invoke(mana);
        }
    }

    public float MaxMana => maxMana;
    public int MaxHealth => maxHealth;

    public int CurrentPlayerDefense
    {
        get => defenseBaseValue;
        set => defenseBaseValue += value;
    }

    private void Awake()
    {
        rb = GetComponent<Rigidbody2D>();
        anim = GetComponent<Animator>();
        manaTimer = manaTimerMax;

        if (instance == null)
        {
            instance = this;
            DontDestroyOnLoad(gameObject);
        }

        else
        {
            Destroy(gameObject);
        }

        Stats = new Stats(new StatsMediator(), baseStats);
    }

    void Start()
    {
        SceneManager.sceneLoaded += OnSceneLoaded;
    }

    private void Update()
    {
        Stats.Mediator.Update(Time.deltaTime);

        if (!IsDead() && canMove)
        {
            if (!isRuning)
            {
                //Get Keyboard Input values
                horizontalInput = Input.GetAxisRaw("Horizontal");
                verticalInput = Input.GetAxisRaw("Vertical");
            }

            else
            {
                //Get Keyboard Input values
                horizontalInput = Input.GetAxisRaw("Horizontal") * 2;
                verticalInput = Input.GetAxisRaw("Vertical") * 2;
            }

            RecoverMana();
            PlayerDefend();
            PlayerAttack();
            PlayerAnimController();
            Run();

            if (Input.GetKeyDown(KeyCode.L))
            {
                anim.SetTrigger("Hurt");
            }

            if (Input.GetKeyDown(KeyCode.R))
            {
                health = 0;
            }

            if (Input.GetKeyDown(InputManager.GetKey(InputAction.Interact)))
            {
                if (interactibleGameObject.TryGetComponent(out IInteractable interaction))
                    interaction.OnInteract();

                else if (interactibleGameObject.TryGetComponent(out IInteractableAsync asyncInteraction))
                    asyncInteraction.OnInteractAsync();
            }
        }

        else if (IsDead())
        {
            InvokeDeath();
        }

        if (Input.GetKeyDown(KeyCode.V))
        {
            RevivePlayer();
        }

        if (Input.GetKeyDown(KeyCode.C))
        {
            IsTalking();
        }
    }

    private void FixedUpdate()
    {
        if (!IsDead() && canMove)
        {
            PlayerMovement();
        }
    }

    private void DestroyObj()
    {
        Destroy(this);
    }

    private void Run()
    {
        if (Input.GetKey(InputManager.GetKey(InputAction.Run)) && Mathf.Round(mana) > 0)
        {
            isRuning = true;
            Mana = Mathf.Max(0, mana - manaDecreaseRate * Time.deltaTime);
            manaTimer = manaTimerMax;
        }

        else isRuning = false;
    }



    //Player Movement
    private void PlayerMovement()
    {
        if (isRuning)
        {
            //Set velocity based on the input
            rb.velocity = new Vector2(horizontalInput, verticalInput).normalized * runSpeed;

            if (rb.velocity != Vector2.zero) lastInput = new Vector2(horizontalInput, verticalInput).normalized;
        }

        else
        {
            //Set velocity based on the input
            rb.velocity = new Vector2(horizontalInput, verticalInput).normalized * speed;

            if (rb.velocity != Vector2.zero) lastInput = new Vector2(horizontalInput, verticalInput).normalized;
        }
    }

    //If player runs out of mana or if it stops to perform an action that costs mana
    //The mana will have a timer to recover
    private void RecoverMana()
    {
        if (mana != maxMana)
        {

            if (manaTimer > 0f)
                manaTimer -= Time.deltaTime;

            else
            {
                Mana = Mathf.Max(Mathf.Clamp((mana + manaIncreaseRate * Time.deltaTime), 0, maxMana));
            }
        }
    }

    //Player Attack
    private void PlayerAttack()
    {

        if (Input.GetKey(InputManager.GetKey(InputAction.Attack)) && mana > 0.1f)
        {
            isAttacking = true;
            manaTimer = maxAttackTimer;
            if (attackTimer <= maxAttackTimer) attackTimer += Time.deltaTime;

            if (attackTimer < maxAttackTimer)
            {
                Mana = Mathf.Max(0, mana - manaDecreaseRate * Time.deltaTime);
            }
        }

        if (Input.GetKeyUp(InputManager.GetKey(InputAction.Attack)) && isAttacking)
        {
            Vector2 origin = transform.position;
            Vector2 direction = lastInput;
            anim.SetBool("IsAttacking", true); //Perform attack Animation
            anim.SetTrigger("BasicAttack");

            RaycastHit2D hit = Physics2D.Raycast(origin, direction, attackRange, enemyLayerMask);

            Debug.DrawRay(origin, direction * attackRange, Color.red, 1.5f);

            if (hit.collider != null)
            {
                Debug.Log($"Collider: {hit.collider}");

                if (hit.collider.TryGetComponent(out IHealthSystem healthSystem))
                {
                    int damage = attackBaseValue + (int)(attackBaseValue * attackTimer);
                    healthSystem.TakeDamage(damage);
                    if (hit.collider.gameObject.TryGetComponent(out Rigidbody2D rb))
                    {
                        rb.AddForce(lastInput * attackForce, ForceMode2D.Impulse);
                    }
                }
            }

            attackTimer = 0f; // Reset attack timer
            isAttacking = false;
            // Ensure the animation can play out by delaying the reset of IsAttacking
            StartCoroutine(ResetAttackAnimation());
        }
    }

    private void OnTriggerStay2D(Collider2D other)
    {
        interactibleGameObject = other.gameObject;
    }


    // Coroutine to delay resetting the attack animation
    private IEnumerator ResetAttackAnimation()
    {
        yield return new WaitForSeconds(0.1f);  // Adjust the delay as necessary
        anim.SetBool("IsAttacking", false);
        anim.ResetTrigger("BasicAttack");
    }


    private void PlayerDefend() => shield = Input.GetKey(KeyCode.Space);

    //Set Animation Values
    private void PlayerAnimController()
    {
        anim.SetFloat("MoveX", horizontalInput);
        anim.SetFloat("MoveY", verticalInput);
        anim.SetFloat("LastMoveX", lastInput.x);
        anim.SetFloat("LastMoveY", lastInput.y);
        anim.SetFloat("Velocity", new Vector2(horizontalInput, verticalInput).normalized.magnitude);
    }

    public void TakeDamage(int amount)
    {
        Health = Mathf.Max(0, Health - amount);
    }
    public void Heal(int amount) => health += amount;
    public int CurrentHealth() => health;
    public bool IsDead() => health <= 0f ? true : false;

    private void InvokeDeath()
    {
        anim.SetTrigger("IsDead");
        rb.velocity = Vector2.zero;
    }

    private void RevivePlayer()
    {
        health = 100;
        anim.ResetTrigger("IsDead");
        anim.Play("Idle", 0);
    }

    private void IsTalking()
    {
        isTalking = !isTalking;
        anim.SetBool("Chat", isTalking);
    }

    public void Accept(IVisitor visitor) => visitor.Visit(this);

    void OnSceneLoaded(Scene scene, LoadSceneMode mode)
    {
        if (scene.name == "MainMenu")
        {
            Destroy(gameObject);
        }
    }

    void OnDestroy()
    {
        SceneManager.sceneLoaded -= OnSceneLoaded;
    }
}