﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Photon.Pun;
using UnityEngine.SceneManagement;
using PhotonHashTable = ExitGames.Client.Photon.Hashtable;

public class PlayerBase : MonoBehaviourPunCallbacks
{
	[Header("Internal References")]
    #region
    [Tooltip("Stores the Character Controller reference")]
    [ReadOnly] public CharacterController CharacterController;
    [Tooltip("Stores the Animator reference")]
    [ReadOnly] public Animator Animator;
    [Tooltip("Hinge location for head rotations")]
	[ReadOnly] public GameObject NeckPivot;
    [Tooltip("Left Hand reference")]
    [ReadOnly] public Transform LeftHand;
    [Tooltip("Drain Beam line renderer reference")]
    [ReadOnly] public LineRenderer DrainBeam;
    [Tooltip("Right Hand reference")]
    [ReadOnly] public Transform RightHand;
    [Tooltip("Build Wand reference")]
    [ReadOnly] public GameObject BuildWand;
    #endregion

    [Header("External References")]
    #region
    [Tooltip("Stores the Audio Manager reference")]
    [ReadOnly] public AudioManager AudioManager;
    [Tooltip("Stores the Spectator Controller reference")]
    [ReadOnly] public SpectatorController SpectatorController;
    [Tooltip("Focal point for camera rotations")]
	[ReadOnly] public Transform CameraPivot;
    //[Tooltip("Stores the Base Camera controls")]
	//[ReadOnly] public BasicCameraBehaviour BasicCameraBehaviour;
    #endregion

    [Header("Item References")]
    #region
    [Tooltip("Stores the Active Wand script")]
    [ReadOnly] public NewWandScript ActiveWandScript;
    [Tooltip("Stores the Active Potion script")]
    [ReadOnly] public PotionScript ActivePotionScript;
    [Tooltip("Stores the Drain Target reference")]
    [ReadOnly] public GameObject DrainTarget, DrainedTarget;
    #endregion

    [Header("UI References")]
    #region
    [Tooltip("Stores the end-of-round UI reference")]
    [ReadOnly] public RoundOverDisplay RoundOverDisplay;
    [Tooltip("Stores the counter for players defeated")]
    [ReadOnly] public DefeatedCounterDisplay DefeatCounter;
    [Tooltip("Stores the UI canvas")]
    [ReadOnly] public CanvasHandler CanvasHandler;
    [Tooltip("Stores the hit marker UI")]
    [ReadOnly] public HitMarker HitMarker;
    [Tooltip("Pause and Quit script reference")]
    [ReadOnly] public PauseAndQuit PauseAndQuit;
    [Tooltip("Item UI reference")]
    [ReadOnly] public ItemUI ItemUI;
    [Tooltip("Inventory selection border reference")]    
    [ReadOnly] public RectTransform SelectionBorder;
    [Tooltip("Health bar text display")]	
    [ReadOnly] public Text HealthText;
    [Tooltip("Health bar rect reference")]    
    [ReadOnly] public RectTransform HealthBar;
    [Tooltip("Mana bar text display")]    
    [ReadOnly] public Text ManaText;
    [Tooltip("Mana bar rect reference")]    
    [ReadOnly] public RectTransform ManaBar;
    [Tooltip("Charge bar rect reference")]    
    [ReadOnly] public RectTransform ChargeBar;
    [Tooltip("Energy bar text display")]    
    [ReadOnly] public Text EnergyText;
    [Tooltip("Energy bar rect reference")]    
    [ReadOnly] public RectTransform EnergyBar;
    #endregion

    [Header("Player Start Stats")]
    #region
    [Range(0, 100)] public float Health = 100;
    [Range(0, 100)] public float Mana = 0;
    [Range(0, 100)] public float ManaRegenRate = 10;
    [Range(0, 500)] public float BuildEnergy = 0;
    [Range(0, 5)] public float ManaExhaustedDelay = 3;
    #endregion

    [Header("Movement Values")]
    #region
    [ReadOnly] public float jumpSpeed = 10f;
	[ReadOnly] public float baseSpeed = 8f;
    [ReadOnly] public float sprintSpeed = 15f;
    [ReadOnly] public float camSpeed = 5f;
    [ReadOnly] public float gravity = 20f;
    #endregion

    [Header("Rotation Data")]
    #region
    [Tooltip("Rotation weight given to keyboard rotation inputs")]
    [ReadOnly] public float keySensitivity = 0.5f;
    [Tooltip("Euler representation of the player's current rotation")]
    [ReadOnly] public Vector3 CurrentRotation;
    [Tooltip("Euler representation of the camera pivot point's current rotation")]
    [ReadOnly] public Vector3 PivotRotation;
    #endregion

    [Header("Inventory Items")]
    #region
    [ReadOnly] public int activeItemNum = 0;
	[ReadOnly] public int m_lastItemNum = 0;
    [ReadOnly] public GameObject ActiveWand;
    [ReadOnly] public GameObject ActivePotion;
	[ReadOnly] public List<GameObject> Inventory;
	[ReadOnly] public GameObject EquippedItem = null;
    [ReadOnly] public GameObject HealthPotion;
    #endregion

    [Header("Drain Stats")]
    #region
    [ReadOnly] public float drainSpeed = 0.5f;
    [ReadOnly] public float gainedEnergy = 0f;
    #endregion

    [Header("Fire Inputs")]
    #region
    [ReadOnly] public bool RepeatFire;
    [ReadOnly] public bool Charging;
    #endregion

    [Header("Misc")]
    #region
    [ReadOnly] public EnergyList BuildEnergies;
    [ReadOnly] public PlayerBase LastAttacker;
	[ReadOnly] public List<AudioClip> EmoteAudioLoops;
    #endregion

    [Header("Debug")]
    #region
    public bool enableDebugMessages;
    #endregion

    // PUBLIC VALUES //
    #region
    public bool Building { get { return m_building; } }
    public bool Drinking { get; private set; }
    public bool Spectating { get { return m_spectating; } set { m_spectating = value; } }
    public bool Defeated { get; private set; }
    public bool CanMove { get; private set; }
    public float Speed { get { return m_currentSpeed; } private set { m_currentSpeed = value; } }
    public bool Sprinting { get; private set; }
    public bool Jumped { get; private set; }
    public bool InAir { get; private set; }
    public bool InStorm { get; set; }
    public bool OutOfBounds { get { return m_outOfBounds; } set { m_outOfBounds = value; } }
    public bool ManaBurnout { get; private set; }
    public bool ManaOnCooldown { get; private set; }
	public Ray MagicRay { get; private set; }

    [HideInInspector] public Vector3 ForceVelocity = Vector3.zero;
    #endregion

    // PRIVATE VALUES //
    #region
	private float m_currentSpeed;
	private List<float> m_slowEffects = new List<float>();
    private bool m_spectating;
    private float m_heightOffset = 1f;
	private int m_jumpOffset = 0;
    private float m_yRot;	
    private bool m_outOfBounds = false;
    [SerializeField] private bool m_building = false;
	private Vector3 m_magicDir;
	private float m_gScale;
    private bool m_mouseLocked = false;
    private bool m_usingMenu = false;
    private bool m_canDrain;
    private bool m_canStartFire = true;
    private bool m_casting;
    private bool m_draining = false;
    private bool m_energyFull = false;
    private Vector3 m_forwardVelocity = Vector3.zero;
    private Vector3 m_horizontalVelocity = Vector3.zero;
    private Vector3 m_upwardVelocity = Vector3.zero;
    private Vector3 m_moveDirection = Vector3.zero;
    private Vector2[] m_selectSpots = new Vector2[] {new Vector2(-240, 60), new Vector2(-190, 60), new Vector2(-140, 60), new Vector2(-90, 60), new Vector2(-40, 60), new Vector2(-190, 110)};  // Stores offset points around the player for building
    #endregion

    // DEFAULT METHODS //
    #region
    void Awake()
    {
        // Set up default player values
        InitializePlayer();
		
        // If we are connected to a photon network...
        if (PhotonNetwork.IsConnected && !IsNetworkPlayer())
        {
            // ...update the properties for the photon hash table
            PhotonHashTable update = new PhotonHashTable() { { "remaining", true } };
            PhotonNetwork.LocalPlayer.SetCustomProperties(update);
        }
    }
    void FixedUpdate()
    {
        if (IsNetworkPlayer())
            return;

        HandleMana();
    }
	void Update ()
    {
        if (IsNetworkPlayer())
            return;

		AdjustedMouse ();
        HandleSpectate();
		HandleInputs ();
		HandleCamPivot ();
		HandleEnergy ();
		UpdateHUDValues ();

		SelectionBorder.anchoredPosition = m_selectSpots [activeItemNum];
		BuildWandRandomizer.isActive = BuildWand.GetComponent<MeshRenderer> ().enabled;
	
		if (EquippedItem != null && !EquippedItem.GetComponent<PotionScript> ()) {
			Animator.SetLayerWeight (1, 1f);
		} else {
			Animator.SetLayerWeight (1, 0f);
		}
	}
    #endregion

    // HANDLER METHODS //
    #region
    /// <summary>
    /// Handles all input cases
    /// </summary>
    private void HandleInputs ()
    {
        HandleMouseCapture();

        // If we have been defeated, return
        if (Defeated)
            return;

        // Check if our player is able to move
        HandleMobility();

        // If we can't move, reset input and animation values
        if (!CanMove)
            ResetInputsAndAnims();

        // If we can move...
        if (CanMove)
        {
            // Update our movement inputs
            HandleMovementInput();

            // If we are not drinking a potion...
            if (!Drinking)
            {
                // Update item inputs
                HandleItemInputs();

                // Update inventory inputs
                HandleInventoryInputs();
            }

            // Update potion inputs
            HandlePotionInput();
        }

        // Update the persistent rotation data to match the camera's
        CurrentRotation.y = CameraPivot.eulerAngles.y;

        // Set the rotations of the player to lerp towards that persistent rotation if we aren't in an emote menu or animation
        transform.eulerAngles = (AnimationWheel.toggleWheel || AnimationWheel.emoteActive) ? transform.eulerAngles : Vector3.Lerp(transform.eulerAngles, CurrentRotation, 1);

        // Create a euler rotation for the head opposite of the body
        //Vector3 neckRot = new Vector3(-transform.eulerAngles.y, 0f, 0f);

        // Apply the rotation
        NeckPivot.transform.eulerAngles = transform.eulerAngles;

        // Set the energy beam's origin to the left hand
        DrainBeam.SetPosition(0, LeftHand.transform.position);

        // If we can't drain, set the energy beam's end point to the left hand as well
        if (!m_canDrain)
            DrainBeam.SetPosition(1, LeftHand.transform.position);

        // Set up the magic ray
        MagicRay = Camera.main.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0f));

        // Draw a line to represent our magic ray
        Debug.DrawLine(MagicRay.origin, MagicRay.origin + MagicRay.direction * 3, Color.red);
    }

    /// <summary>
    /// Handles mouse capture behaviors
    /// </summary>
    private void HandleMouseCapture()
    {
        // If we have any menu open (round over, pause menu, map, emote wheel))
        m_usingMenu = (RoundOverDisplay && RoundOverDisplay.IsRoundOver()) || (PauseAndQuit && PauseAndQuit.paused) || MapScript.toggleMap || AnimationWheel.toggleWheel;

        if (!m_usingMenu)
        {
            // Left Alt serves as our general screen lock toggle
            if (Input.GetKeyDown(KeyCode.LeftAlt))
                m_mouseLocked = !m_mouseLocked;

            // Clicking on the screen locks the mouse
            if (Input.GetMouseButtonDown(0))
                m_mouseLocked = true;
        }

        Cursor.visible = !m_mouseLocked || m_usingMenu;
        Cursor.lockState = m_mouseLocked && !m_usingMenu ? CursorLockMode.Locked : CursorLockMode.None;
    }

    /// <summary>
    /// Handles camera turning behaviors
    /// </summary>
    private void HandleCamPivot ()
    {
        // If we do not have a camera pivot...
        if(!CameraPivot)
        {
            // Find an object with that tag and return
            GameObject _pivot = GameObject.FindGameObjectWithTag("Camera Pivot");

            // If we couldn't find one, return
            if (!_pivot)
                return;

            // Otherwise, set the reference to be the camera pivot's transform
            CameraPivot = _pivot.transform;
        }

		CameraPivot.position = new Vector3 (transform.position.x, transform.position.y + m_heightOffset, transform.position.z);

        if (Cursor.visible)
            return;

		PivotRotation += new Vector3 (AdjustedMouse ().x * camSpeed, AdjustedMouse ().y * camSpeed, 0f);

		if (PivotRotation.x > 35f) {
			PivotRotation.x = 35f;
		} else if (PivotRotation.x < -65f) {
			PivotRotation.x = -65f;
		}

		CameraPivot.eulerAngles = PivotRotation;
	}
    /// <summary>
    /// Updates the m_canMove state depending on obstructive circumstances
    /// </summary>
    public void HandleMobility()
    {
        CanMove = !AnimationWheel.toggleWheel && !(Cursor.lockState == CursorLockMode.None);
    }
    /// <summary>
    /// Handles movement input detection
    /// </summary>
	private void HandleMovementInput ()
    {
		// Update our Sprinting value
		Sprinting = (Input.GetKey (KeyCode.LeftShift)) ? true : false;

        // Set our movement speed according to that value
		Speed = (Sprinting) ? sprintSpeed : baseSpeed;

        // Set our animation values accordingly
		float _animFloat = (Sprinting) ? 2f : 1f;
		Animator.SetFloat ("xDir", Input.GetAxis ("Horizontal") * _animFloat);
		Animator.SetFloat ("yDir", Input.GetAxis ("Vertical") * _animFloat);

		// Jump handling and animation triggers
		if (Physics.Raycast (transform.position, Vector3.down, CharacterController.bounds.extents.y)) {
			if (!CharacterController.isGrounded) {
				m_moveDirection.y -= gravity * Time.deltaTime;
			} else {
				if (InAir) {
					InAir = false;
					Animator.SetTrigger ("Land");
					m_moveDirection.y = 0f;
					Jumped = false;
					m_jumpOffset = 0;
				}
				if (Input.GetButton ("Jump")) {
					InAir = true;
					Animator.SetTrigger ("Jump");
					Jumped = true;
					m_moveDirection.y = jumpSpeed;
				}
			}
		} else {
			if (!Jumped && m_jumpOffset > 1) {
				Animator.SetTrigger ("Fall");
				InAir = true;
				Jumped = true;
			}
			m_jumpOffset++;
			m_moveDirection.y -= gravity * Time.deltaTime;
		}

	    float _slowPerc = CalcSlowPerc();

		// Set the movement velocities and move direction
	    m_forwardVelocity = Input.GetAxis ("Vertical") * transform.forward * Speed * (1 - _slowPerc);
	    m_horizontalVelocity = Input.GetAxis ("Horizontal") * transform.right * Speed * (1 - _slowPerc);
		m_upwardVelocity = transform.up * m_moveDirection.y;
	    //m_moveDirection = new Vector3(Input.GetAxis("Horizontal") * Speed, m_moveDirection.y, Input.GetAxis("Vertical") * Speed);

		// Animate and move the controller
		//bool running = (Input.GetAxisRaw ("Horizontal") != 0f || Input.GetAxisRaw ("Vertical") != 0) ? true : false;
		CharacterController.Move((m_forwardVelocity + m_horizontalVelocity + m_upwardVelocity + ForceVelocity) * Time.deltaTime);

        if (ForceVelocity != Vector3.zero)
        {
            ForceVelocity *= 0.5f;
            ForceVelocity = Mathf.Abs(ForceVelocity.magnitude) < 0.1f ? Vector3.zero : ForceVelocity;
        }
	}
    /// <summary>
    /// Handles inputs for wands and potions
    /// </summary>
	private void HandleItemInputs ()
    {
        // Check for EnergyDrain behaviors
        HandleEnergyDrain();

        // Handle Magic Attacks
        HandleWand();
    }
    /// <summary>
    /// Handles Energy Drain behaviors
    /// </summary>
    private void HandleEnergyDrain ()
    {
        // If we are not holding the builder wand, return
        if (activeItemNum != 5)
            return;

        // Create a temporary field for a RaycastHit result
        RaycastHit _hit;

        // If the secondary fire button is being held down...
		if ((Input.GetButtonDown ("Fire2")  || Input.GetKeyDown (KeyCode.U)))
            // ...and our magic ray hits something...
            if (Physics.Raycast (MagicRay, out _hit))
                // ...and what we hit does have a collider...
			    if (_hit.collider != null)
                    // ...and what we hit has a drain script, is within range, and we are not full on energy...
				    if (CheckForDrainScript (_hit.collider.gameObject) && _hit.distance <= 40f && BuildEnergy < 500)
                    {
                        // Get the GameObject of the collider
					    DrainedTarget = _hit.collider.gameObject;

                        // Update our canDrain state
					    m_canDrain = true;

                        // Activate the right arm drain animation
					    Animator.SetLayerWeight (2, 1f);
					    Animator.SetTrigger ("RightClick");
					    Animator.SetBool ("Drain", true);
				    }

        // If our input is being held down...
		if (Input.GetButton ("Fire2")  || Input.GetKey (KeyCode.U))
        {
            // ...and we started draining a legal object...
			if (m_canDrain)
            {
                // ...and the magic ray still is hitting something...
				if (Physics.Raycast (MagicRay, out _hit))
                {
                    // If our raycast is hitting a different target or we are out of range
                    if (!_hit.collider.gameObject == DrainedTarget || _hit.distance > 40f)
                    {
                        // Stop draining and return out
                        StopDrain();
                        return;
                    }

                    // If we are still on a valid target, then set the energy beam's position to our hit location
					DrainBeam.SetPosition (1, _hit.point);

                    // Also update our draining state
					m_draining = true;

                    // If the object
					if (!DrainTarget.GetComponent<MaterialDrainValue> ().TryDrain (this))
						StopDrain ();
				}
                else
					StopDrain ();
			}

            return;
		}
        else if (Input.GetButtonUp ("Fire2")  || Input.GetKeyUp (KeyCode.U))
			if (m_canDrain)
				Animator.SetBool ("Drain", false);
        else
			if (m_draining)
            {
				StopDrain ();
				m_draining = false;
			}
	}
    /// <summary>
    /// Handles inventory inputs
    /// </summary>
	private void HandleInventoryInputs ()
    {
        //Debug.Log("Checking Inputs");
        if (!Input.anyKeyDown)
        {
            //Debug.Log("No Inputs");
            return;
        }

        // Deactivate any emotes
        AnimationWheel.emoteActive = false;

        if (Input.GetKeyDown(KeyCode.Q))
        {
            // The build wand's number is "5". If the number is 5, swap back to the previous item on the hotbar
            // Otherwise, swap to 5.
            activeItemNum = (activeItemNum == 5) ? m_lastItemNum : 5;

            // Toggle the Building state
            m_building = (activeItemNum == 5);

            if (enableDebugMessages)
                Debug.Log("Swapping " + (m_building ? "to" : "from") + " build mode.");

            // Set the active item to the inventory index
            SetActiveItem(Inventory[activeItemNum]);
            UpdateNetworkEquipped();

            return;
        }

        // If the player is pressing F and we have an item equipped that is NOT the build wand, drop it
		if (Input.GetKeyDown (KeyCode.F) && EquippedItem != null && activeItemNum != 5)
        {
            DropItem (EquippedItem);
            return;
        }

        // If our build wand is active, skip the rest of this
        if (activeItemNum == 5)
            return;
        
        // Set the active item number depending on these inputs
		if (Input.GetKeyDown (KeyCode.Alpha1))
			activeItemNum = 0;
        else if (Input.GetKeyDown (KeyCode.Alpha2))
			activeItemNum = 1;
        else if (Input.GetKeyDown (KeyCode.Alpha3))
			activeItemNum = 2;
        else if (Input.GetKeyDown (KeyCode.Alpha4))
			activeItemNum = 3;
        else if (Input.GetKeyDown (KeyCode.Alpha5))
			activeItemNum = 4;
        else if (Input.GetAxisRaw ("Mouse ScrollWheel") < 0)
			activeItemNum = (activeItemNum == 4) ? 0 : activeItemNum + 1;
        else if (Input.GetAxisRaw ("Mouse ScrollWheel") > 0)
			activeItemNum = (activeItemNum == 0) ? 4 : activeItemNum - 1;

        // If the last item number is not equal to the active number, we have swapped
        if (m_lastItemNum != activeItemNum)
        {
            if (enableDebugMessages)
                Debug.Log("Swapping to inventory slot " + activeItemNum.ToString() + "!");

            SetActiveItem(Inventory[activeItemNum]);
            UpdateNetworkEquipped();
            m_lastItemNum = activeItemNum;
        }
	}
    /// <summary>
    /// Handles wand activation behaviors
    /// </summary>
    private void HandleWand()
    {
        // If we have an active wand object...
        if (ActiveWand)
        {

            // Get the wand's data
            ActiveWandScript = ActiveWand.GetComponent<NewWandScript>();

            // If we couldn't find it, return
            if (!ActiveWandScript)
            {
                if (enableDebugMessages)
                    Debug.Log("Player [ID: " + photonView.ViewID.ToString() + "] does not have a valid wand in their hands.");

                return;
            }
            
            // If we have a fire input and are not using the build wand...
            if ((Input.GetButton("Fire1") || Input.GetKey(KeyCode.H)) && !m_canDrain && m_canStartFire)
            {
                if (enableDebugMessages)
                    Debug.Log("Player [ID: " + photonView.ViewID.ToString() + "] has started a few fire input.");

                m_canStartFire = false;

                UpdateCastAnims(true, false, true);
                ActiveWandScript.FireWand();
            }

            if (Input.GetButtonUp("Fire1") || Input.GetKeyUp(KeyCode.H))
            {
                if (enableDebugMessages)
                    Debug.Log("Player [ID: " + photonView.ViewID.ToString() + "] has released a fire input.");

                UpdateCastAnims(false, false, false);
                ActiveWandScript.EndFireWand();

                StartCoroutine(WaitAfterFire());
            }
        }
    }
    /// <summary>
    /// Handles potion drinking inputs
    /// </summary>
    private void HandlePotionInput ()
    {
        // If our active object is a potion...
		if (ActivePotion)
            // ...and we get a left-mouse input while not yet drinking...
			if ((Input.GetButtonDown ("Fire1") || Input.GetKeyDown (KeyCode.H)) && !Drinking && Health < 100)
            {
                // Decrement the active potion stack size
				ActivePotionScript.stackNumber--;

                // Update the inventory display stack number
				ItemDisplays.itemDisplay.UpdateAmount (ActivePotionScript);
                
                // Set the potion to active
				ActivePotion.SetActive (true);

                // Update our drinking state
				Drinking = true;

                // Set the drinking arm animation weight
				Animator.SetLayerWeight (3, 1f);

                // Call our animation trigger
				Animator.SetTrigger ("Drink");

                StartCoroutine(DrinkPotion());
			}
	}
    /// <summary>
    /// Handles behavior when a player finishes drinking a potion
    /// </summary>
	public void HandleDrankPotion ()
    {
        // Deactivate the active potion object
		ActivePotion.SetActive (false);

        // Accrue the player's health value by the potion's value
		Health += ActivePotionScript.potion.value;

        if (Health > 100)
            Health = 100;

        // Set the drinking arm animation back to default
		Animator.SetLayerWeight (3, 0f);

        // If the number of potions in this inventory slot is at or below zero...
		if (ActivePotionScript.stackNumber <= 0)
        {
            // Drop the potion gameobject
			DropItem (ActivePotionScript.gameObject);

            // If we are the host of the photon network, make the destruction call across the network
			if (PhotonNetwork.IsConnected && PhotonNetwork.IsMasterClient)
				PhotonNetwork.Destroy (ActivePotionScript.gameObject);
            // Otherwise, simply destroy it locally
            else
				Destroy (ActivePotionScript.gameObject);
		}

        // Update our drinking state
		Drinking = false;
	}
    /// <summary>
    /// Handles mana regen behaviors
    /// </summary>
    private void HandleMana ()
    {
        // If we are on a refresh delay, return
        if (ManaBurnout || ManaOnCooldown)
            return;

        // If mana is at or below zero...
		if (Mana <= 0)
        {
            if (enableDebugMessages)
                Debug.Log("Mana is depleted and will begin recharging after a delay.");

            StartCoroutine(ManaEmpty());
            return;
        }

        // If we are not using mana, we are not on a delay and our mana is not full...
        if (!Charging && !RepeatFire && !ManaOnCooldown && Mana < 100)
        {
            // Increase our mana value by the regen rate times the fixed delta interval
            Mana = Mana + ManaRegenRate * Time.fixedDeltaTime;

            // Clamp to 100
            Mana = Mana > 100 ? 100 : Mana;
        }
	}
    /// <summary>
    /// Handles energy clamping behaviors
    /// </summary>
	private void HandleEnergy ()
    {
        // Accumulate the total amount of energy from our BuildEnergies list
		BuildEnergy = BuildEnergies.TotalEnergy ();

        // If we are at or above 500, clamp it and set our "full" state to true
		if (BuildEnergy >= 500f)
        {
			BuildEnergy = 500f;
			m_energyFull = true;
            return;
		}
        // If we are below zero, clamp it to zero
        else if (BuildEnergy < 0f)
			BuildEnergy = 0f;

        // We only reach this if we are not full. Toggle the "full" state if we are set as full.
        if (m_energyFull)
		    m_energyFull = false;
	}
    /// <summary>
    /// Handles defeat behaviors
    /// </summary>
    private void HandleDefeat()
    {
        // If the player is already defeated, return
        if (Defeated)
            return;

        // Otherwise, note that we are defeated!
        Defeated = true;

        // If we are on a network, update the network properties of the client
        if (PhotonNetwork.IsConnected)
        {
            PhotonHashTable update = new PhotonHashTable() { { "remaining", false } };
            PhotonNetwork.LocalPlayer.SetCustomProperties(update);
        }

        //Play defeat animations
        Animator.SetLayerWeight(1, 0f);
        Animator.SetLayerWeight(2, 0f);
        Animator.SetTrigger("Defeat");

        DropAllItems();
        StartCoroutine(SwitchToSpectatorMode());
    }
    /// <summary>
    /// Handles spectate behaviors, such as locating active spectator controllers and handling spectator-specific camera movement.
    /// </summary>
    private void HandleSpectate()
    {
        // If this a local player, return
        if (!photonView || !PhotonNetwork.IsConnected || photonView.IsMine)
            return;

        // If there is no SpectatorController reference, verify it
        if (!SpectatorController)
            VerifySpectatorController();

        // If this player is being spectated, handle camera behaviors
        if (m_spectating && SpectatorController && !SpectatorController.freeFlying)
            HandleCamPivot();
    }
    #endregion

    // VALIDATE METHODS //
    #region
    /// <summary>
    /// Called in the case of a missing SpectatorController reference.
    /// </summary>
    private void VerifySpectatorController()
    {
        // Get all of the SpectatorControllers in the scene
        SpectatorController[] foundObjects = FindObjectsOfType<SpectatorController>();

        // If we found any, store only the first instance
        if (foundObjects.Length > 0)
            SpectatorController = foundObjects[0];
    }
    #endregion

    // SETTER METHODS //
    #region
    /// <summary>
    /// Sets the active item to the given GameObject
    /// </summary>
    /// <param name="_newItem"></param>
    public void SetActiveItem(GameObject _newItem)
    {
        // ABORT CIRCUMSTANCES //
        #region
        // If we were passed a null value, empty the active items and return
        if (!_newItem)
        {
            if (enableDebugMessages)
                Debug.Log("Attempting to set active item to a null reference. Unequipping all items.");

            UnequipAllItems();
            return;
        }

        // If the item we were passed has no Item Base, return
        if (!_newItem.GetComponent<ItemBase>() && !_newItem.tag.ToLower().Contains("builder"))
        {
            if (enableDebugMessages)
                Debug.Log("Attempting to set active item to a gameobject that is not an item. Unequipping all items.");

            _newItem.SetActive(false);
            UnequipAllItems();
            return;
        }

        if (EquippedItem == _newItem)
        {
            if (enableDebugMessages)
                Debug.Log("Aborted item swapping because the new item is already equipped.");

            return;
        }
        #endregion

        // CANCEL FIRE INPUTS //
        #region
        // If the player is holding the fire input...
        if (RepeatFire || Charging)
        {
            if (enableDebugMessages)
                Debug.Log("Player [ID: " + photonView.ViewID.ToString() + "] fire input cancelled due to item switching.");

            // Cancel the current animation
            Animator.SetTrigger ("Cancel");

            // Reset autofire
			RepeatFire = false;

            // Reset charging
			Charging = false;

            // Reset firing
            if (ActiveWandScript)
                ActiveWandScript.EndFireWand(true);
		}
        #endregion

        // HANDLE CURRENT ITEM //
        #region
        // Unequip all current active and equipped items
        UnequipAllItems();
        #endregion

        // HANDLE NEW ITEM //
        #region
        // Set the active item to the new item
        EquippedItem = _newItem;

        if (EquippedItem.tag.ToLower().Contains("builder"))
        {
            // Enable the mesh renderer and return
            EquippedItem.GetComponent<MeshRenderer>().enabled = true;
            return;
        }

        // If the active item is a wand, activate it
		if(EquippedItem.GetComponent<ItemBase>().itemType == ItemType.Wand) { }
           	EquippedItem.SetActive(true);
        
        // Set the item as Equipped
        EquippedItem.GetComponent<ItemBase>().SetEquipped(true);

        // If this is a wand...
        if (EquippedItem.GetComponent<NewWandScript>())
        {
            // Set the wand to be the currently equipped item
            ActiveWand = EquippedItem;

            // Set the wand script to be the active wand script
            ActiveWandScript = EquippedItem.GetComponent<NewWandScript>();
        }
        // If this is a potion...
        else if (EquippedItem.GetComponent<PotionScript>())
        {
            // Set the potion to be the currently equipped item
            ActivePotion = EquippedItem;
                
            // Set the potion script to be the active potion script
            ActivePotionScript = EquippedItem.GetComponent<PotionScript>();
        }
        #endregion
    }
    #endregion

    // GETTER METHODS //
    #region
    /// <summary>
    /// Returns the player being controlled on this device
    /// </summary>
    /// <returns></returns>
    public static PlayerBase GetLocalPlayer()
    {
        // Get all of the gameobjects with the "player" tag
        GameObject[] players = GameObject.FindGameObjectsWithTag("Player");

        // Return the player with the photon view that is marked as "IsMine"
        for (int i = 0; i < players.Length; i++)
            if (players[i].GetPhotonView().IsMine)
                return players[i].GetComponent<PlayerBase>();

        // If there are no client players, return null
        return null;
    }
    /// <summary>
    /// Returns a player GameObject with the given View ID. If one cannot be found, returns null.
    /// </summary>
    /// <param name="_ID"></param>
    /// <returns></returns>
    public static GameObject GetPlayerByID(int _ID)
    {
        // Get all of the objects tagged as "player" in the scene
        GameObject[] _players = GameObject.FindGameObjectsWithTag("Player");

        // Return the first player that has a matching ID
        foreach (GameObject player in _players)
            if (player.GetComponent<PlayerBase>().photonView.Owner.ActorNumber == _ID)
                return player;

        // If we don't find any, return null
        return null;
    }
    /// <summary>
    /// Returns the first available network instance of a player in a multiplayer room. If we are not in multiplayer or there are no other player instances, nothing will be returned.
    /// </summary>
    /// <returns></returns>
    public static Transform GetNetworkPlayer()
    {
        // We cannot spectate if we are not online, so return null
        if (!PhotonNetwork.IsConnected)
            return null;

        // Get all players in the scene
        GameObject[] players = GameObject.FindGameObjectsWithTag("Player");

        // For each player in the array...
        for(int i = 0; i < players.Length; i++)
        {
            // Get that player's photonView
            PhotonView view = players[i].GetPhotonView();

            // We only return network instances of players
            if (view && !view.IsMine)
                return players[i].transform;
        }

        // If we couldn't find a network player, return null
        return null;
    }
    /// <summary>
    /// Returns true if the player is a Photon instance
    /// </summary>
    /// <returns></returns>
    public bool IsNetworkPlayer()
    {
        return photonView && !photonView.IsMine && PhotonNetwork.IsConnected;
    }
    /// <summary>
    /// Returns the degree of mouse movement scaled by the sensitivity settings.
    /// </summary>
    /// <returns></returns>
    private Vector2 AdjustedMouse()
    {
        // Set up temporary fields to hold our inputs
        float _xAccess = 0f;
        float _yAccess = 0f;

        // I input
        if (Input.GetKey(KeyCode.I))
            _xAccess -= 1 * keySensitivity;

        // K input
        if (Input.GetKey(KeyCode.K))
            _xAccess += 1 * keySensitivity;

        // J input
        if (Input.GetKey(KeyCode.J))
            _yAccess -= 1 * keySensitivity;

        // L input
        if (Input.GetKey(KeyCode.L))
            _yAccess += 1 * keySensitivity;

        // Apply mouse inputs
        _xAccess -= Input.GetAxis("Mouse Y");
        _yAccess += Input.GetAxis("Mouse X");

        // Return the inputs as a Vector2
        return new Vector2(_xAccess, _yAccess);
    }
    /// <summary>
    /// Checks if the given object is a drainable object
    /// </summary>
    /// <param name="_obj"></param>
    /// <returns></returns>
	private bool CheckForDrainScript (GameObject _obj)
    {
		if (_obj.GetComponent<MaterialDrainValue> ())
        {
			if (!_obj.GetComponent<MaterialDrainValue> ().IsDepleted)
            {
				DrainTarget = _obj;
				return true;
			}
		}
        else if (_obj.transform.parent)
			return CheckForDrainScript (_obj.transform.parent.gameObject);

		DrainTarget = null;
		return false;
    }
	float CalcSlowPerc(){
		if (m_slowEffects == null)
			m_slowEffects = new List<float>();
			
		if (m_slowEffects.Count == 0)
			return 0;
			
		float _accumulatedSlow = 0;
			
		foreach (float _effect in m_slowEffects){
			_accumulatedSlow += (1 - _accumulatedSlow) * Mathf.Clamp01(_effect);
		}
		
		return Mathf.Clamp(_accumulatedSlow, 0.25f, 1);
	}
    #endregion

    // EXECUTABLE METHODS //
    #region
    /// <summary>
    /// Initializes default player values
    /// </summary>
    private void InitializePlayer()
    {
        // DEVELOPER NOTE - It would be better at some point to move this to a ScriptableObject preset for custom gamemodes

        CanMove = true;
        Sprinting = false;
        InAir = false;
        Defeated = false;
        Jumped = false;
        Drinking = false;
        ManaBurnout = false;
        ManaOnCooldown = false;
        InStorm = false;

        // Player has not been attacked yet
        LastAttacker = null;

        // Players are playing by default, not spectating
        m_spectating = false;

        // Sounds are played adjacent to the camera; if we don't have a reference to it yet, acquire it from the main camera.
        if (!AudioManager)
            AudioManager = GameObject.FindGameObjectWithTag("MainCamera").GetComponent<AudioManager>();

        // If this player is a network instance
        if (IsNetworkPlayer())
        {
            Inventory.Clear();
            Inventory.Add(BuildWand);
            return;
        }

        // Set up the default values of a player at the start of a game
        Health = 100f;
        Mana = 0f;
        BuildEnergy = 0f;
        Speed = baseSpeed;
        m_canDrain = false;

        // Set the energy beam's origin and end point to the left hand
        DrainBeam.SetPosition(0, LeftHand.transform.position);
        DrainBeam.SetPosition(1, LeftHand.transform.position);

        // Find the animator and character controller component from the player
        Animator = GetComponent<Animator>();
        CharacterController = GetComponent<CharacterController>();

        // Clear the inventory of items
        Inventory.Clear();

        // Set up the Build Energies list
        BuildEnergies.list.Clear();
        BuildEnergies.list = new List<Energy>();

        // Populate our inventory list so that we have six slots
        while (Inventory.Count < 6)
            Inventory.Add(null);

        // Set the 6th slot to be our build wand
        Inventory[5] = BuildWand;

        // Set up the camera pivot point
        if (!CameraPivot)
        {
            CameraPivot = GameObject.FindGameObjectWithTag("Camera Pivot").transform;

            if (ItemDisplays.itemDisplay)
                ItemDisplays.itemDisplay.playerBase = this;
        }

        // Set up the pause menu reference
        if (!PauseAndQuit)
        {
            PauseAndQuit = GameObject.FindGameObjectWithTag("Pause Menu").GetComponent<PauseAndQuit>();
        }

        // Set up the selection boarder reference
        if (!SelectionBorder)
        {
            SelectionBorder = GameObject.FindGameObjectWithTag("Selection Border").GetComponent<RectTransform>();
        }

        // Set up the health bar reference
        if (!HealthBar)
        {
            HealthBar = GameObject.FindGameObjectWithTag("Health Bar").GetComponent<RectTransform>();
        }

        // Set up the mana bar reference
        if (!ManaBar)
        {
            ManaBar = GameObject.FindGameObjectWithTag("Mana Bar").GetComponent<RectTransform>();
        }

        // Set up the charge bar reference
        if (!ChargeBar)
        {
            ChargeBar = GameObject.FindGameObjectWithTag("Charge Bar").GetComponent<RectTransform>();
        }

        // Set up the health bar reference
        if (!EnergyBar)
        {
            EnergyBar = GameObject.FindGameObjectWithTag("Energy Bar").GetComponent<RectTransform>();
        }

        // Set up the health text reference
        if (!HealthText)
        {
            HealthText = GameObject.FindGameObjectWithTag("Health Text").GetComponent<Text>();
        }

        // Set up the mana text reference
        if (!ManaText)
        {
            ManaText = GameObject.FindGameObjectWithTag("Mana Text").GetComponent<Text>();
        }

        // Set up the energy text reference
        if (!EnergyText)
        {
            EnergyText = GameObject.FindGameObjectWithTag("Energy Text").GetComponent<Text>();
        }

        // Set up the item UI reference
        if (!ItemUI)
        {
            ItemUI = GameObject.FindGameObjectWithTag("Item UI").GetComponent<ItemUI>();
        }

        // Set up the hit marker reference
        if (!HitMarker)
        {
            HitMarker = GameObject.FindGameObjectWithTag("HitMarker").GetComponent<HitMarker>();
        }

        // Set up the health bar reference
        //if (!BasicCameraBehaviour)
        //{
        //    BasicCameraBehaviour = CameraPivot.GetChild(0).GetComponent<BasicCameraBehaviour>();
        //}

        // Set up the canvas handler reference
        if (!CanvasHandler)
        {
            // Get the object tagged as our Game Manager
            GameObject _gameManager = GameObject.FindGameObjectWithTag("Game Manager");

            // If we didn't get one, return
            if (!_gameManager)
                return;

            // Set the canvas handler reference
            CanvasHandler = _gameManager.GetComponent<CanvasHandler>();

            // Set up the round over display reference
            if (!RoundOverDisplay)
            {
                RoundOverDisplay = _gameManager.GetComponent<RoundOverDisplay>();
                SpectatorController = RoundOverDisplay.gameObject.GetComponent<SpectatorController>();
            }

            // Set up the defeat counter reference
            if (!DefeatCounter)
                DefeatCounter = _gameManager.GetComponent<DefeatedCounterDisplay>();
        }
    }
    /// <summary>
    /// Resets inputs and animation values to default
    /// </summary>
    private void ResetInputsAndAnims ()
    {
		Animator.SetFloat ("xDir", 0f);
		Animator.SetFloat ("yDir", 0f);
		Animator.SetLayerWeight (3, 0f);
		UpdateCastAnims (false, false, false);

		// Handle floating with Animation Wheel
		if (Physics.Raycast (transform.position, Vector3.down, CharacterController.bounds.extents.y)) {
			if (!CharacterController.isGrounded) {
				m_moveDirection.y -= gravity * Time.deltaTime;
			} else {
				if (InAir) {
					InAir = false;
					Animator.SetTrigger ("Land");
					m_moveDirection.y = 0f;
					Jumped = false;
					m_jumpOffset = 0;
				}
			}
		} else {
			if (!Jumped && m_jumpOffset > 1) {
				Animator.SetTrigger ("Fall");
				InAir = true;
				Jumped = true;
			}
			m_jumpOffset++;
			m_moveDirection.y -= gravity * Time.deltaTime;
		}

		m_upwardVelocity = transform.up * m_moveDirection.y;
		m_moveDirection = new Vector3(0f, m_moveDirection.y, 0f);
		CharacterController.Move((m_upwardVelocity) * Time.deltaTime);
	}
    /// <summary>
    /// Culls draining behaviors
    /// </summary>
	public void StopDrain ()
    {
        // If this player is a network instance, we do not have authority over it and simply return
        if (photonView && PhotonNetwork.IsConnected && !photonView.IsMine)
            return;

        // If we have an object we are draining, get the energy we have accrued thusfar
		if (DrainTarget)
			DrainTarget.GetComponent<MaterialDrainValue>().FinalizeEnergyDrain(this);

        // Reset the canDrain state
        m_canDrain = false;

        // Set the drain speed to zero
		drainSpeed = 0f;

        // Empty the drained object reference
		DrainTarget = null;

        // Empty the raycast target reference
		DrainedTarget = null;

        // Update the player's energy value
		HandleEnergy ();

        // Lerp out of the drain animation
		StartCoroutine (DrainBeGone ());
	}
	/// <summary>
    /// Makes photon calls to make remote inventory swaps
    /// </summary>
	private void UpdateNetworkEquipped ()
    {
        // If we are not on a photon network, return
        if (!photonView || !PhotonNetwork.IsConnected)
            return;

        // If the inventory slot is not empty and has a photonview...
		if (Inventory [activeItemNum] && Inventory [activeItemNum].GetPhotonView ())
        {
            // RPC call to swap the current item
			PhotonDebugger.IncrementNumberOfMessages ("switch");
			photonView.RPC ("SwitchItem", RpcTarget.OthersBuffered, photonView.ViewID, Inventory [activeItemNum].GetPhotonView ().ViewID);
		}
        // If the active item is the builder wand...
        else if (activeItemNum == 5)
        {
            // RPC call to swap the current item
			PhotonDebugger.IncrementNumberOfMessages ("switch");
			photonView.RPC ("SwitchItem", RpcTarget.OthersBuffered, photonView.ViewID, -2);
		}
        // If the inventory reference is empty...
        else if (Inventory [activeItemNum] == null)
        {
            // RPC call to swap to nothing!
			PhotonDebugger.IncrementNumberOfMessages ("switch");
			photonView.RPC ("SwitchItem", RpcTarget.OthersBuffered, photonView.ViewID, -1);
		}
	}
    /// <summary>
    /// Empties the any equipped item references
    /// </summary>
    private void UnequipAllItems()
    {
        // If we have an active wand, disable it
        if (ActiveWand)
        {
            ActiveWand.SetActive(false);
            ActiveWand = null;
        }

        // Null the active wand script reference
        if (ActiveWandScript)
            ActiveWandScript = null;

        // If we have an active potion, disable it
        if (ActivePotion)
        {
            ActivePotion.SetActive(false);
            ActivePotion = null;
        }

        // Null the active potion script reference
        if (ActivePotionScript)
            ActivePotionScript = null;

        // If we have an equipped item...
        if (EquippedItem)
        {
            // If that item is a build wand...
            if (EquippedItem.tag.ToLower().Contains("builder"))
            {
                // Disable the mesh renderer
                EquippedItem.GetComponent<MeshRenderer>().enabled = false;

                // Null the equipped item reference and return
                EquippedItem = null;
                return;
            }

            // If that item has an ItemBase script...
            if (EquippedItem.GetComponent<ItemBase>())
                    EquippedItem.GetComponent<ItemBase>().SetEquipped(false);

            // Disable the equipped item
            EquippedItem.SetActive(false);

            // Clear the equipped item reference
            EquippedItem = null;
        }

        // No matter what, we can no longer drain
        m_canDrain = false;
    }
    /// <summary>
    /// Unequips any equipped items of the given type
    /// </summary>
    private void NullActiveItems(ItemType type)
    {
        switch(type)
        {
            case (ItemType.Wand):
                if (ActiveWand)
                {
                    ActiveWand.SetActive(false);
                    ActiveWand = null;
                }
                break;
            case (ItemType.Potion):
                if (ActivePotion)
                {
                    ActivePotion.SetActive(false);
                    ActivePotion = null;
                }
                break;
        }
    }
    /// <summary>
    /// Updates casting animation data and states
    /// </summary>
    /// <param name="_auto"></param>
    /// <param name="_charge"></param>
    /// <param name="_trigger"></param>
	private void UpdateCastAnims (bool _auto, bool _charge, bool _trigger)
    {
        // Update our autofire and charging states
		RepeatFire = _auto;
		Charging = _charge;

        // Set the matching animation values
		Animator.SetBool ("Auto", _auto);
		Animator.SetBool ("Charged", _charge);

        // If firing began this frame, activate the animation trigger to start the transition
		if(_trigger)
			Animator.SetTrigger ("LeftClick");
	}
    /// <summary>
    /// Plays a foostep sound from the audio manager
    /// </summary>
    private void PlayFootStepSound() {
        AudioManager.PlaySound("footsteps", transform.position);
    }
    /// <summary>
    /// Changes health/mana/charge/energy bar sizes in the HUD
    /// </summary>
	private void UpdateHUDValues ()
    {
        // Health bar behaviors
		if (HealthBar)
        {
            // Set the size delta to our health value times 2.5 (between 0 and 250)
			HealthBar.sizeDelta = new Vector2 (2.5f * Health, HealthBar.sizeDelta.y);

            // Create a string from our health value rounded (no decimals)
            string displayHealth = (Health <= 0) ? "0" : Mathf.CeilToInt(Health).ToString ();

            // Update the display text
            HealthText.text = displayHealth + " / 100";
		}
        // Same for Mana
		if (ManaBar)
        {
			ManaBar.sizeDelta = new Vector2 (2.5f * Mana, ManaBar.sizeDelta.y);
			string displayMana = (Mana <= 0) ? "0" : Mathf.RoundToInt (Mana).ToString ();
			ManaText.text = displayMana + " / 100";
		}
        // Same for Charge
        if (ChargeBar)
        {
            // Only difference here is that we don't display charge if we don't have an active wand, and it has no text component
            float value = ActiveWand ? Mathf.Clamp(2.5f * ActiveWand.GetComponent<NewWandScript>().ManaCharged, 0, 250) : 0;
            ChargeBar.sizeDelta = new Vector2(value, ChargeBar.sizeDelta.y);
        }
        // Same for Energy
		if (EnergyBar) {
			EnergyBar.sizeDelta = new Vector2 (0.29f * BuildEnergy, EnergyBar.sizeDelta.y);
			string displayEnergy = (BuildEnergy <= 0) ? "0" : Mathf.RoundToInt (BuildEnergy).ToString ();
			EnergyText.text = displayEnergy + " / 500";
		}
	}
    /// <summary>
    /// Deals damage to the local player
    /// </summary>
    /// <param name="_damage"></param>
    /// <param name="_attacker"></param>
    /// <returns></returns>
    public void DoDamage(float _damage, PlayerBase _attacker = null)
    {
        // If we are in the launcher or esports launcher, return false
        if (SceneManager.GetActiveScene().name.ToLower().Contains("tutorial") || SceneManager.GetActiveScene().name.ToLower().Contains("launcher"))
            return;

        // If we have a round over display AND the display is registering that the round is over, return false
        if (RoundOverDisplay && RoundOverDisplay.IsRoundOver())
            return;

        // If we were passed a player that dealt damage, set that as the last attacker
        if (_attacker)
            LastAttacker = _attacker;

        // Subtract the damage
        Health = Mathf.Ceil(Health - _damage);

        // If health is above zero...
        if (Health > 0)
            return;

        // We do this to make sure the player only sends out one defeated message
        Health = 0;

        // If we are on a network...
        if (photonView.IsMine && PhotonNetwork.IsConnected)
            // Make an RPC call to defeat network instances of this player
            photonView.RPC("PlayerDefeated", RpcTarget.OthersBuffered, _attacker ? _attacker.photonView.ViewID : -1, photonView.ViewID);
        
        // We were defeated. Oh nooooooooo!
        HandleDefeat();
    }
	public void AddSlow(float _perc, float _duration = 0){
		if (_perc <= 0)
			return;
		
		m_slowEffects.Add(Mathf.Clamp01(_perc));
		
		if (_duration > 0)
			RemoveSlowAfterDelay(_perc, _duration);
	}
	async void RemoveSlowAfterDelay(float _perc, float _delay){
		System.Threading.Tasks.Task.Delay((int)(_delay) * 1000);
		RemoveSlow(_perc);
	}
	public void RemoveSlow(float _perc){
		if (m_slowEffects.Contains(_perc))
			m_slowEffects.Remove(_perc);
	}
    /// <summary>
    /// Reduces Mana amount by a given amount and activates regen delay behaviors
    /// </summary>
    /// <param name="_manaLost"></param>
    public void ExpendMana(float _manaLost)
    {
        if (enableDebugMessages)
            Debug.Log(_manaLost.ToString() + " mana expended with a remaining value of " + (Mana - _manaLost > 0 ? Mana - _manaLost : 0).ToString() + " mana remaining.");

        // If we are losing more or as much mana as we have available...
        if (_manaLost >= Mana)
        {
            // Start the Mana Empty coroutine and return
            StartCoroutine(ManaEmpty());
            return;
        }

        // Decrease Mana by the mana expended
        Mana -= _manaLost;

        // Delay mana regen
        StartCoroutine(DelayMana());
    }
    /// <summary>
    /// Calls DropItem for every item in inventory
    /// </summary>
    private void DropAllItems()
    {
        // For each item in our inventory, so long as the inventory slot is not empty, drop it!
        for(int i = 0; i < Inventory.Count-1; i++)
            if(Inventory[i])
                DropItem(Inventory[i], 500);
    }
    /// <summary>
    /// Handles Item Drop behavior
    /// </summary>
    /// <param name="_item"></param>
    /// <param name="_forceMagnitude"></param>
    /// <param name="_direction"></param>
	public void DropItem (GameObject _item, float _forceMagnitude = 0, Vector3 _direction = new Vector3()) {

		// If the builder wand is currently our active item...
		if (EquippedItem == BuildWand)
        {
            Debug.Log("Building is not off");
            // Disable building
			m_building = false;

            // Equip the last item we had equipped
			activeItemNum = m_lastItemNum;
			SetActiveItem (Inventory [activeItemNum]);
			UpdateNetworkEquipped ();

            // Move the item to replace our argument
			_item = EquippedItem;
			if (_item == null)
				return;
		}

        // If the dropped item is our currently equipped one, clear the active wand reference
        if (ActiveWand == _item)
			ActiveWand = null;
		
        // Same for potions
        if (ActivePotion == _item)
		    ActivePotion = null;

        // Get the itemscript of the item we need to drop. This includes Wands and Potions.
        ItemBase itemScript = _item.GetComponent<ItemBase>();

        // If the item does not have an itemScript, destroy it.
        if (!itemScript)
            Destroy(_item);

        // Handle drop behaviors
        itemScript.HandleDrop();

        // Get the index number of the item
        int inventoryIndex = Inventory.IndexOf(_item);

        // If the number is valid, nullify that index
        if (inventoryIndex >= 0)
            Inventory[Inventory.IndexOf(_item)] = null;

        // Null the activeItem
        EquippedItem = null;

        // Try to get the descendent version of the item (wand or potion)
        NewWandScript wand = _item.GetComponent<NewWandScript>();
        PotionScript potion = _item.GetComponent<PotionScript>();

        // If we found a wand, hide show its particle effects
        //if (wand)
        //    wand.HideParticleEffects(false);
        
        // If this is a network instanced player...
		if (IsNetworkPlayer())
        {
            // Remove any null references from the network player's inventory list
            Inventory.RemoveAll(GameObject => GameObject == null);
            return;
        }

        // Get the item's photon view
        PhotonView itemPhotonView = _item.GetComponent<PhotonView>();

        // If we found one...
        if (itemPhotonView)
        {
            // ...and our eject force is greater than zero...
            if (_forceMagnitude > 0)
            {
                // If we don't have a direction, randomize the yaw
                if (_direction == Vector3.zero)
                    _direction = Quaternion.Euler(0, UnityEngine.Random.Range(0, 360), 0) * Vector3.forward;

                // If we found a wand, add force to it 
                if (wand)
                    wand.AddForceToItem(_forceMagnitude, _direction);
                
                // If we found a potion, do the same
                if (potion)
                    potion.AddForceToItem(_forceMagnitude, _direction);
            }
            
            // If we are on the network and a player spawned this item...
			if (PhotonNetwork.IsConnected && photonView.Owner != null)
                if (!itemScript.canStack)
                {
                    PhotonDebugger.IncrementNumberOfMessages("drop item");
                    itemPhotonView.RPC("DropItem", RpcTarget.OthersBuffered, photonView.Owner.ActorNumber, itemPhotonView.ViewID, _forceMagnitude, _direction);
                }
                else
                {
                    PhotonDebugger.IncrementNumberOfMessages("drop item stack");
                    itemPhotonView.RPC("DropItemStack", RpcTarget.OthersBuffered, photonView.Owner.ActorNumber, itemPhotonView.ViewID, 0f, Vector3.zero, itemScript.stackNumber);
                }
        }

        ItemDisplays.itemDisplay.RemoveItem (activeItemNum);
	}
    #endregion

    // COROUTINES //
    #region
    /// <summary>
    /// Interpolates from the drain animation back to default
    /// </summary>
    /// <returns></returns>
	IEnumerator DrainBeGone ()
    {
        // Create temp fields for the timer and layer weight
		float _t = 0;
		float _layerWeight = 1;

        // While the timer is below 1 and we don't have a drain target...
		while (_t < 1 && !m_canDrain)
        {
            // Increment _t by the frame time times 3
            // This effectively makes the reset time 0.33 seconds
			_t += Time.deltaTime * 3f;

            // Set the layer weight of the arm to 0 by the timer factor
			_layerWeight = Mathf.Lerp (1, 0, _t);
			Animator.SetLayerWeight (2, _layerWeight);

            // Yield for the next frame
			yield return null;
		}

        // Done!
		yield return null;
	}
    /// <summary>
    /// Resets mana after falling below zero
    /// </summary>
    /// <returns></returns>
	IEnumerator ManaEmpty ()
    {
        if (enableDebugMessages)
            Debug.Log("Mana is empty and will be reset.");

        // Set mana back to zero and mark that we're on cooldown
		Mana = 0f;
        ManaOnCooldown = true;

        // Wait to be refreshed...
		yield return new WaitForSecondsRealtime (ManaExhaustedDelay);

        // We set the mana value to be above zero to avoid triggering another delay
        Mana = 0.01f;

        StartCoroutine(DelayMana());

        // End Coroutine
		yield return null;
	}
    /// <summary>
    /// Enables mana regen delay after firing a spell
    /// </summary>
    /// <returns></returns>
    IEnumerator DelayMana()
    {
        // Our delay is active
        ManaOnCooldown = true;

        // Wait for the delay to finish...
        yield return new WaitForSeconds(0.25f);

        // Delay is done!
        ManaOnCooldown = false;
        
        // End coroutine
        yield return null;
    }
    /// <summary>
    /// Delays input commands after releasing the fire button
    /// </summary>
    /// <returns></returns>
    IEnumerator WaitAfterFire()
    {
        if (enableDebugMessages)
            Debug.Log("Player [ID: " + photonView.ViewID + "] is waiting to enable fire again.");

        yield return new WaitForSeconds(0.5f);

        if (enableDebugMessages)
            Debug.Log("Player [ID: " + photonView.ViewID + "] is can fire again.");

        m_canStartFire = true;

        yield break;
    }/// <summary>
    /// Enables mana regen delay after firing a spell
    /// </summary>
    /// <returns></returns>
    IEnumerator DrinkPotion()
    {
        // Wait for the delay to finish...
        yield return new WaitForSeconds(1);

        HandleDrankPotion();

        // End coroutine
        yield return null;
    }
    /// <summary>
    /// Handles swapping from Play mode to Spectator
    /// </summary>
    /// <returns></returns>
    IEnumerator SwitchToSpectatorMode() {
		if (!OutOfBounds) {
			yield return new WaitForSeconds (3f);
		}

        if(RoundOverDisplay && !RoundOverDisplay.IsRoundOver()) {
            Transform t;
            SpectatorController spectatorController = RoundOverDisplay.gameObject.GetComponent<SpectatorController>();
            if (!LastAttacker) {
                t = GetNetworkPlayer();
            }
            else {
                t = LastAttacker.transform;
            }

            if (!t) {
                if (PhotonNetwork.IsConnected)
                    PhotonNetwork.Destroy(photonView);
                else
                    Destroy(gameObject);
                yield break;
            }

            spectatorController.SpectatePlayer(t);

            if (CanvasHandler)
                CanvasHandler.SetCanvasAsActive(CanvasHandler.CanvasType.Spectator);
            else
                Debug.Log("No Canvas Handler Set");
        }
        
        if (PhotonNetwork.IsConnected)
            PhotonNetwork.Destroy(photonView);
        else
            Destroy(gameObject);
    }
    #endregion

    // RPC METHODS //
    #region
    [PunRPC] public void SwitchItem(int playerID, int itemID) {
        if (photonView.ViewID != playerID) {
            return;
        }
		if (itemID == -1) {
            UnequipAllItems();
			return;
		} else if (itemID == -2) {
			SetActiveItem (Inventory [(photonView.IsMine ? Inventory.Count - 1 : 0)]);
			return;
		}
        foreach(GameObject obj in Inventory) {
            if (!obj)
                continue;
            if(obj.GetPhotonView() && obj.GetPhotonView().ViewID == itemID) {
                SetActiveItem(obj);
                return;
            }
        }
    }
    /// <summary>
    /// Deals damage to the Victim based on the incoming Damage value
    /// </summary>
    /// <param name="_projectileViewID"></param>
    /// <param name="_victimViewID"></param>
    /// <param name="_ownerViewID"></param>
    /// <param name="_damage"></param>
    [PunRPC] public void ProjectileHit(int _victimViewID, int _ownerViewID, float _damage)
    {
        // If this player is a network instance...
        if (!photonView.IsMine)
        {
            // Get the client player
            PlayerBase _localPlayer = GetLocalPlayer();

            // If they are found, run this function for them
            if(_localPlayer)
                _localPlayer.ProjectileHit(_victimViewID, _ownerViewID, _damage);
        
        }

        Debug.Log("Got a hit!");
        
        // If we are the owner of the spell that hit this player
        if (_ownerViewID == photonView.ViewID) 
        {
            // Make the hitmarker appear!
            HitMarker.PlayHitmarker();
        }

        // Create a field for the projectile's owner
        PlayerBase _projectileOwner = null;

        // Get all of the players in the game
        GameObject[] allPlayers = GameObject.FindGameObjectsWithTag("Player");

        // Set the projectileOwner field to the player that matches the owner ID
        for (int i = 0; i < allPlayers.Length; i++)
            if (allPlayers[i].GetPhotonView().ViewID == _ownerViewID)
                _projectileOwner = allPlayers[i].GetComponent<PlayerBase>();

        // If this player is the victim and it is also the client...
        if (_victimViewID == photonView.ViewID && photonView.IsMine)
        {
            // Calculate whether or not we have sustained enough damage to be defeated
            DoDamage(_damage, _projectileOwner);
        }
    }
    /// <summary>
    /// Calls for the player to be defeated across the Photon network
    /// </summary>
    /// <param name="_VictimID"></param>
    /// <param name="_OwnerViewID"></param>
    [PunRPC] public void PlayerDefeated(int _VictimID, int _OwnerViewID)
    {
        // If this is not the client player...
        if(!photonView.IsMine)
        {
            // Get the client player
            PlayerBase localPlayer = GetLocalPlayer();

            // Run the same method on them instead (this seems redundant)
            if (localPlayer)
                localPlayer.PlayerDefeated(_VictimID, _OwnerViewID);
        }

        // If this is the client player and we are the one that defeated that player, update the matching display
        if (photonView.IsMine && _OwnerViewID == photonView.ViewID)
            DefeatCounter.SetDefeatedPlayersCount(DefeatCounter.GetDefeatedPlayersCount() + 1);
    }
    #endregion
    
    // DEPRECATED METHODS //
    #region
    // trying to figure out how to get a rage quit / disconnect to drop their inventory
    //void OnDestroy() {
    //    if (SceneManager.GetActiveScene().name == "Launcher") {
    //        return;
    //    }
    //    if (photonView && photonView.Owner == null && !PhotonNetwork.IsMasterClient) {
    //        return;
    //    }

    //    if(!defeated) {
    //        DropAllItems();
    //    }
    //}
    #endregion
}


