Hark-the-Moon-Men-Cometh / Assets / Scripts / Creatures / PlayerAvatar.cs
PlayerAvatar.cs
Raw
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;


//A lot of functionality flows through this class. It should probably be split
//up a bit, but this is a first draft and right now getting it working is more
//important than making it tidy
public class PlayerAvatar : Creature, IAttacker
{
    private static int ID_COUNT = 0;

    private static List<PlayerAvatar> PLAYER_AVATARS;

    //This isn't neccessary yet, but when multiplayer is added it might be
    //useful. Returns null if player doesn't exist
    public static PlayerAvatar GetPlayerWithID(int id)
    {
        foreach (PlayerAvatar p in PLAYER_AVATARS)
        {
            if (p.m_id == id) return p;
        }
        return null;
    }

    public static List<PlayerAvatar> GetPlayerList()
    {
        return PLAYER_AVATARS;
    }

    public int m_id;

    public RectTransform r_hpMaxBar;
    public RectTransform r_hpValueBar;

    private Rigidbody m_rb;

    //these control most of the movement and jumping abilities
    private bool m_isImmobile;
    public float m_speed;
    public float m_jumpForce;
    public float m_lookSpeed;
    public float m_jumpAirControlSpeed;
    public float m_maxTraversibleSlopeAngle;

    private bool m_isJumping;
    private bool m_isDoubleJumping;
    public float m_doubleJumpDelay;
    private float m_doubleJumpTimer;
    public bool m_canJump;
    //needed when slowing strafe speed mid-air
    private Vector3 m_jumpStartMomentum;
    //For temllin when you're on a jumpable surface
    private List<ContactPoint> m_contacts;

    public int m_currentWeaponID;
    public PlayerWeapon m_currentWeapon;
    public List<PlayerWeapon> m_weapons;

    public Transform m_aimOrigin;

    public bool m_isOTSCamera;

    //List of controls. Some of these will need to be renamed later because they
    //weill be doing different things depending on context.
    private InputAction m_attackAction;
    private InputAction m_moveAction;
    private InputAction m_jumpAction;
    private InputAction m_lookAction;
    private InputAction m_secondaryAction;
    private InputAction m_interactAction;

    private InputAction m_changeWeaponsAction;
    private bool m_isChangeWeaponPressed;

    protected override void Awake()
    {
        base.Awake();
        if(PLAYER_AVATARS is null)
        {
            PLAYER_AVATARS = new List<PlayerAvatar>();
        }
        PLAYER_AVATARS.Add(this);
        m_id = ID_COUNT++;
        m_currentWeaponID = 0;
        m_isJumping = false;
        m_canJump = false;
        m_contacts = new List<ContactPoint>();
        m_isChangeWeaponPressed = false;
        m_isOTSCamera = false;
        m_isImmobile = false;
        m_isDoubleJumping = false;
        m_doubleJumpTimer = 0;
    }

    protected override void Start()
    {
        base.Start();
        m_rb = GetComponent<Rigidbody>();
        //If the sleep threshold is >0, the avatar starts moving in weird ways.
        m_rb.sleepThreshold = 0f;
        m_attackAction = InputSystem.actions.FindAction("Attack");
        m_moveAction = InputSystem.actions.FindAction("Move");
        m_jumpAction = InputSystem.actions.FindAction("Jump");
        m_lookAction = InputSystem.actions.FindAction("Look");
        m_secondaryAction = InputSystem.actions.FindAction("Secondary");
        m_interactAction = InputSystem.actions.FindAction("Interact");
        m_changeWeaponsAction = InputSystem.actions.FindAction("CycleWeapons");
    }

    protected override void FixedUpdate()
    {
        EquipWeapons();
        Look();
        base.FixedUpdate();
    }

    protected override void UpdateUIElements()
    {
        UpdateHPBar();
    }

    private void UpdateHPBar()
    {
        //scales hpbar based on current max health, incase that changes
        float pixelPerHP = r_hpMaxBar.localScale.x / m_maxHealth;
        float currentHPBarWidth = pixelPerHP * m_currentHealth;
        r_hpValueBar.localScale = new Vector3(currentHPBarWidth, 1f, 1f);
    }

    protected void EquipWeapons()
    {
        //if no weapon is equiped it breaks a lot of shit
        if (m_currentWeapon is null)
        {
            m_currentWeapon = m_weapons[0];
            m_currentWeapon.Equip();
        }
        //switch weapons
        else if (m_changeWeaponsAction.IsPressed() && !m_isChangeWeaponPressed
            && !m_currentWeapon.IsAttacking())
        {
            m_isChangeWeaponPressed = true;
            m_currentWeapon.UnEquip();
            m_currentWeaponID = (m_currentWeaponID + 1) % m_weapons.Count;
            m_currentWeapon = m_weapons[m_currentWeaponID];
            m_currentWeapon.Equip();
        }
        if (!m_changeWeaponsAction.IsPressed())
        {
            m_isChangeWeaponPressed = false;
        }
    }

    protected override void HandleWeapon()
    {
        //attack action
        if (m_attackAction.IsPressed() && !m_currentWeapon.IsAttacking()
            && !m_isJumping && !m_isTakingDamage)
        {
            m_currentWeapon.Attack();
        }
        //secondary isn't neccesarily mutually exclusive with attacking
        //e.g. OTS camera        
        if (m_secondaryAction.IsPressed())
        {
            m_currentWeapon.SecondaryHeld();
        }
        else
        {
            m_currentWeapon.SecondaryReleased();
        }
    }

    public void SetIsAttacking(bool attacking)
    {
        m_currentWeapon.SetIsAttacking(attacking);
    }

    protected override void DoAction()
    {
        //throw new System.NotImplementedException();
    }

    private void Look()
    {
        //Rotates avatar around Y-axis based on mouse movement. The camera
        //controls X-axis rotation and currently follows the avatar's Y rotation,
        //which is slightly easier than the other way around. This'll be changed 
        //soon though when I switch to free-cam.
        Vector2 lookValue = m_lookAction.ReadValue<Vector2>() * m_lookSpeed
            * Time.deltaTime;
        transform.Rotate(0f, lookValue.x, 0f);
    }

    protected override void Move()
    {
        if (CanMove())
        {
            //turn input to Vector3
            Vector2 moveInputValue =
                m_moveAction.ReadValue<Vector2>().normalized;
            Vector3 moveVector = new Vector3(moveInputValue.x, 0f,
                moveInputValue.y);

            //jump needs to be done before speed modifications or the
            //double-jump doesn't work well
            CheckJumpable();
            Jump(moveVector);

            //conditional speed modifications
            if (m_isJumping)
            {
                moveVector *= m_jumpAirControlSpeed;
                moveVector += m_jumpStartMomentum;
            }
            if (m_isTakingDamage)
            {
                moveVector /= 2f;
            }

            //Move transform
            transform.position += transform.TransformDirection(moveVector)
                    * m_speed * Time.deltaTime;            

            //running animations
            if (!m_isJumping)
            {
                r_animator.SetFloat("MoveX", moveInputValue.x);
                r_animator.SetFloat("MoveY", moveInputValue.y);
                if (moveVector.sqrMagnitude > 0f)
                {
                    r_animator.SetBool("Running", true);
                }
                else
                {
                    r_animator.SetBool("Running", false);
                }
            }
        }
        else
        {
            r_animator.SetBool("Running", false);
        }
    }

    private bool CanMove()
    {
        return !m_isImmobile;
    }

    public void SetPlayerImmobile(bool isImmobile)
    {
        m_isImmobile = isImmobile;
    }

    #region COLLISIONS
    private void OnCollisionStay(Collision collision)
    {        
        foreach (ContactPoint contactPoint in collision.contacts)
        {
            m_contacts.Add(contactPoint);
        }
    }

    private void OnCollisionEnter(Collision collision)
    {
        if (m_isJumping && IsCollisionSurfaceJumpable(collision))
        {
            r_animator.SetTrigger("Landed");
            r_animator.ResetTrigger("Jump");
            m_isJumping = false;
            m_isDoubleJumping = false;
            m_doubleJumpTimer = 0;            
        }                   
    }
    #endregion

    #region JUMPING
    private void Jump(Vector3 moveVector)
    {
        if (m_isJumping)
        {
            m_doubleJumpTimer += Time.deltaTime;
        }
        if (m_jumpAction.IsPressed())
        {            
            //always needs to be done when jumping
            if ((m_canJump && !m_isJumping) || CanDoubleJump())
            {
                m_jumpStartMomentum = moveVector;
                Vector3 jumpVector = Vector3.up * m_jumpForce;
                m_isJumping = true;
                r_animator.SetTrigger("Jump");                
                //only needs to be done when double jumping
                if (CanDoubleJump())
                {
                    jumpVector += moveVector;
                    m_isDoubleJumping = true;
                    r_animator.SetTrigger("DoubleJump");
                }
                m_rb.AddForce(jumpVector, ForceMode.Impulse);
            }                                            
        }
    }

    private bool CanDoubleJump()
    {
        return m_isJumping && !m_isDoubleJumping 
            && m_doubleJumpTimer > m_doubleJumpDelay;
    }

    private void CheckJumpable()
    {
        m_canJump = false;
        int i = 0;
        while (!m_isJumping && !m_canJump && i < m_contacts.Count)
        {
            m_canJump = IsContactPointJumpable(m_contacts[i]);
            i++;
        }
        m_contacts.Clear();
    }

    private bool IsCollisionSurfaceJumpable(Collision collision)
    {
        int i = 0;
        bool jumpable = false;
        while (!jumpable && i < collision.contacts.Length)
        {
            jumpable = IsContactPointJumpable(collision.contacts[i]);
            i++;
        }
        return jumpable;
    }

    private bool IsContactPointJumpable(ContactPoint contact)
    {        
        //Check if the contact point's normal is at an acceptable angle
        float angle = Vector3.Angle(contact.normal, Vector3.up);
        return angle < m_maxTraversibleSlopeAngle;
    }
    #endregion

    #region HEALTH
    public override void TakeDamage(float damage)
    {
        if (!m_isInvulnerable)
        {
            r_animator.SetTrigger("Take Damage");
            base.TakeDamage(damage);
        }
    }

    public override void StopTakingDamage()
    {
        base.StopTakingDamage();
    }

    public override void Die()
    {
        PLAYER_AVATARS.Remove(this);
        Destroy(this.gameObject);
    }

    #endregion

    public Vector3 GetFacing()
    {
        return m_aimOrigin.TransformDirection(Vector3.forward);
    }

    public RaycastHit GetAimTarget()
    {
        LayerMask layerMask =~ LayerMask.GetMask("Player");
        RaycastHit hit;
        Debug.DrawRay(m_aimOrigin.position, GetFacing()*100, 
            Random.ColorHSV(0f, 1f, 1f, 1f, 0.5f, 1f), 2f);
        if (Physics.Raycast(m_aimOrigin.position, GetFacing(), out hit, 
            Mathf.Infinity, layerMask))
        {            
            return hit;
        }
        return hit;
    }    
}