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;
}
}