﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;
using ExitGames.Client.Photon;
using System;

namespace BattleRoyale.Spells
{
    [RequireComponent(typeof(Rigidbody))]
    public class SpellBase : MonoBehaviour
    {
        [Header("Photon IDs")]
        #region
        [Tooltip("The network ID of the player that fired the spell.")]
        [ReadOnly] public int OwnerViewID;
        [Tooltip("The wand that cast this spell")]
        [ReadOnly] public NewWandScript SourceWand;
        #endregion

        [Header("Spell Stats")]
        #region
        [Tooltip("The name of the spell inherited from the catalog spell stats.")]
        [ReadOnly] public string SpellName;
        [Tooltip("The primary type of the spell")]
        /*[ReadOnly]*/ public SpellPrimaryType PrimaryType;
        [Tooltip("The secondary type of the spell")]
        /*[ReadOnly]*/ public SpellSecondaryType SecondaryType;
        [Tooltip("The secondary type of the spell")]
        [ReadOnly] public Rarity Rarity;
        [Tooltip("The stats inherited from the SpellCatalog singleton")]
        [ReadOnly] public SpellStats Stats;
        #endregion

        [Header("Spell Values")]
        #region
        [Tooltip("The current power of the spell")]
        [ReadOnly] public float Damage;
        [Tooltip("The current pushing power of the spell")]
        [ReadOnly] public float PushForce;
        [Tooltip("The current speed of the spell")]
        [ReadOnly] public float Speed;
        [Tooltip("The current size of the spell")]
        [ReadOnly] public float Size;
        [Tooltip("The current target of the spell")]
        [ReadOnly] public GameObject Target;
        [Tooltip("The degrees per second that the spell will change directions to point towards the target")]
        [ReadOnly] public float TrackingRate;
        [Tooltip("The seconds elapsed since the spell was cast")]
        [ReadOnly] public float FlightTime;
        [Tooltip("Cumulative distance travelled since the spell was cast.")]
        [ReadOnly] public float DistanceTravelled;
        [Tooltip("Amount of mana expended when the spell was fired")]
        [ReadOnly] public float ManaCharge;
        #endregion

        [Header("Spell Colors")]
        #region
        /*[ReadOnly]*/ public Color SpellColor1;
        /*[ReadOnly]*/ public Color SpellColor2;
        /*[ReadOnly]*/ public Color SpellColor3;
        /*[ReadOnly]*/ public Color SpellColor4;
        #endregion

        [Header("Spell Audio")]
        #region
        public AudioSource aSource;
        public AudioClip TravelSound;
        #endregion

        [Header("Mesh Renderers")]
        #region
        [Tooltip("Components that will change color to match the first color of the spell")]
        public MeshRenderer[] Color1Meshes = new MeshRenderer[] { };
        [Tooltip("Components that will change color to match the second color of the spell")]
        public MeshRenderer[] Color2Meshes = new MeshRenderer[] { };
        [Tooltip("Components that will change color to match the third color of the spell")]
        public MeshRenderer[] Color3Meshes = new MeshRenderer[] { };
        [Tooltip("Components that will change color to match the fourth color of the spell")]
        public MeshRenderer[] Color4Meshes = new MeshRenderer[] { };
        #endregion

        [Header("Particle Systems")]
        #region
        [Tooltip("Components that will change color to match the first color of the spell")]
        public ParticleSystemRenderer[] Color1Particles = new ParticleSystemRenderer[] { };
        [Tooltip("Components that will change color to match the second color of the spell")]
        public ParticleSystemRenderer[] Color2Particles = new ParticleSystemRenderer[] { };
        [Tooltip("Components that will change color to match the third color of the spell")]
        public ParticleSystemRenderer[] Color3Particles = new ParticleSystemRenderer[] { };
        [Tooltip("Components that will change color to match the fourth color of the spell")]
        public ParticleSystemRenderer[] Color4Particles = new ParticleSystemRenderer[] { };
        #endregion

        [Header("Trail Renderers")]
        #region
        [Tooltip("Components that will change color to match the first color of the spell")]
        public TrailRenderer[] Color1Trails = new TrailRenderer[] { };
        [Tooltip("Components that will change color to match the second color of the spell")]
        public TrailRenderer[] Color2Trails = new TrailRenderer[] { };
        [Tooltip("Components that will change color to match the third color of the spell")]
        public TrailRenderer[] Color3Trails = new TrailRenderer[] { };
        [Tooltip("Components that will change color to match the fourth color of the spell")]
        public TrailRenderer[] Color4Trails = new TrailRenderer[] { };
        #endregion

        [Header("Debug")]
        #region
        [Tooltip("Force the spell to remain in play even when they would normally be destroyed. WARNING - THIS WILL CAUSE GAME-BREAKING ERRORS")]
        public bool forcePersistForTesting;
        [Tooltip("Force the spell to update its component colors on update. WARNING - THIS WILL CAUSE PERFORMANCE ISSUES IF ENABLED DURING GAMEPLAY")]
        public bool realtimeColors;
        [Tooltip("Show debug messages for this spell")]
        public bool enableDebugMessages;
        #endregion

        // DEFAULT METHODS //
        #region
        private void Start()
        {
            InitializeSpell();
        }
        private void Update()
        {
            // Update colors in realtime for testing purposes
            if (realtimeColors)
                UpdateColors();
        }
        private void FixedUpdate()
        {
            if (!m_initialized)
            {
                InitializeSpell();

                if (!m_initialized)
                    return;
            }

            HandleFlightTracking();
            HandleFlightTravel();
            HandleTerrainTravel();
            HandleTargetTracking();
            HandleDynamicValues();
            HandleDOTs();
        }
        private void OnTriggerEnter(Collider other)
        {
            // If a player was hit, handle it
            if (other.GetComponent<PlayerBase>())
                HandlePlayerHit(other.GetComponent<PlayerBase>(), transform.position - other.ClosestPoint(transform.position));
            // If a wall was hit, handle it
            else if (other.transform.tag == "Building")
                HandleWallHit(other.gameObject);
            // If a spell was hit, handle it
            else if (other.GetComponent<SpellBase>())
                HandleSpellHit(other.GetComponent<SpellBase>());
        }
        private void OnTriggerExit(Collider other)
        {
            // If this object is in our damage timers, remove it
	        if (m_damageTimers.ContainsKey(other.gameObject)){
	        	m_timedObjects.Remove(other.gameObject);
		        m_damageTimers.Remove(other.gameObject);
	        }
        }
        #endregion

        // PRIVATE VALUES //
        #region
        private bool m_initialized = false;
        [SerializeField] private float m_baseDamage;
        [SerializeField] private float m_basePushForce;
        [SerializeField] private float m_baseSpeed;
        [SerializeField] private float m_vertVel;
        [SerializeField] private bool m_onGround = false;
        [SerializeField] private bool m_canMove = true;
        [SerializeField] private float m_baseSize;
        [SerializeField] private float m_baseTrackingRate;
        [SerializeField] private float m_lifetime;
	    [SerializeField] private float m_range;
	    private List<GameObject> m_timedObjects = new List<GameObject>();
	    private Dictionary<GameObject, float> m_damageTimers = new Dictionary<GameObject, float>();
        #endregion

        // HANDLER METHODS //
        #region
        /// <summary>
        /// Handles player hit behavior
        /// </summary>
        /// <param name="_col"></param>
        private void HandlePlayerHit(PlayerBase _pb, Vector3 _hitNormal = default(Vector3))
        {
            if (enableDebugMessages)
                Debug.Log("Spell [ID: " + name.Split('_')[1] + "] registered a player hit!");

            // If the player base is null, return
            if (!_pb)
            {
                if (enableDebugMessages)
                    Debug.LogWarning("Spell [ID: " + name.Split('_')[1] + "] player hit failed because it was passed an object with no Player Base!");
                return;
            }

            // If this player is a network instance, return
            if (_pb.IsNetworkPlayer())
            {
                if (enableDebugMessages)
                    Debug.Log("Spell [ID: " + name.Split('_')[1] + "] player hit failed because it was passed a network instance of a player!");
                return;
            }

            // If this player is the owner of the spell and the spell can't hit the owner, return
            if (_pb.photonView.ViewID == OwnerViewID && !Stats.CanHitOwner)
            {
                if (enableDebugMessages)
                    Debug.Log("Spell [ID: " + name.Split('_')[1] + "] player hit failed the player was the caster of the spell and the spell cannot hit its caster!");
                return;
            }

            // If we are not set to hit players, return
            if (!Stats.PlayerBehavior.CanHit)
            {
                if (enableDebugMessages)
                    Debug.Log("Spell [ID: " + name.Split('_')[1] + "] player hit failed because it was passed an object with no Player Base!");
                return;
            }

            // If we deal damage to players...
            if (Stats.PlayerBehavior.Damages)
            {
                // Derive any bonus damage towards players
                float _bonusDamage = Stats.PlayerBehavior.DealsBonusDamage ? (GetCurvedStat(Stats.PlayerBehavior.Graph_BonusDamage, Stats.PlayerBehavior.SP_BonusDamage)) : 0;

                //GameObject _player = null;
                    
                //if (PhotonNetwork.IsConnected)
                //    _player = PlayerBase.GetPlayerByID(OwnerViewID);

                if (enableDebugMessages)
                    Debug.Log("Spell [ID: " + name.Split('_')[1] + "] dealing " + (Damage + _bonusDamage).ToString() + " damage to player [ID: " + _pb.photonView.ViewID.ToString() + "]");

                _pb.photonView.RPC("ProjectileHit", RpcTarget.OthersBuffered, _pb.photonView.ViewID, OwnerViewID, Damage + _bonusDamage);
                // Deal damage and pass through the information about our attacker
                _pb.ProjectileHit(_pb.photonView.ViewID, OwnerViewID, Damage + _bonusDamage);
                
                // If we can hit repeatedly, add the collider to our damage timers list with a 0.5 second timer
	            if (Stats.CanHitRepeatedly && !m_damageTimers.ContainsKey(_pb.gameObject)){
	            	m_timedObjects.Add(_pb.gameObject);
		            m_damageTimers.Add(_pb.gameObject, 0.5f);
	            }
            }

            if (PushForce != 0)
            {
                Vector3 _force = Vector3.zero;

                switch (Stats.ForceDirection)
                {
                    case (SpellForceDirection.LocalForward):
                        _force = Vector3.forward;
                        break;
                    case (SpellForceDirection.LocalBackward):
                        _force = -Vector3.forward;
                        break;
                    case (SpellForceDirection.GlobalUp):
                        _force = Vector3.up;
                        break;
                    case (SpellForceDirection.CenterIn):
                        _force = -(_pb.transform.position - transform.position).normalized;
                        break;
                    case (SpellForceDirection.CenterOut):
                        _force = (_pb.transform.position - transform.position).normalized;
                        break;
                }

                _pb.ForceVelocity = _force * PushForce * 10;

                if (enableDebugMessages)
                    Debug.Log("Spell [ID: " + name.Split('_')[1] + "] applying force of " + PushForce.ToString() + " to player [ID: " + _pb.photonView.ViewID.ToString() + "]");
            }

            // Handle on-hit behaviors
            switch (Stats.PlayerBehavior.CollisionBehavior)
            {
                case (SpellCollisionBehavior.PassesThrough):
                    if (enableDebugMessages)
                        Debug.Log("Spell [ID: " + name.Split('_')[1] + "] passes through players.");
                    break;
                case (SpellCollisionBehavior.DestroyedOnHit):
                    if (enableDebugMessages)
                        Debug.Log("Spell [ID: " + name.Split('_')[1] + "] is destroyed upon hitting a player.");
                    SourceWand.CacheSpellInstance(gameObject);
                    break;
                case (SpellCollisionBehavior.BouncesOff):
                    if (enableDebugMessages)
                        Debug.Log("Spell [ID: " + name.Split('_')[1] + "] bounces off of players.");
                    GetComponent<Rigidbody>().velocity = Vector3.Reflect(GetComponent<Rigidbody>().velocity, _hitNormal);
                    transform.LookAt(transform.position + GetComponent<Rigidbody>().velocity.normalized, Vector3.up);
                    break;
                case (SpellCollisionBehavior.HaltsAt):
                    if (enableDebugMessages)
                        Debug.Log("Spell [ID: " + name.Split('_')[1] + "] stops moving on contact with players.");
                    GetComponent<Rigidbody>().velocity = Vector3.zero;
                    break;
                case (SpellCollisionBehavior.SticksTo):
                    if (enableDebugMessages)
                        Debug.Log("Spell [ID: " + name.Split('_')[1] + "] sticks to players.");
                    if (!transform.parent || !transform.parent.GetComponent<PlayerBase>())
                    {
                        GetComponent<Rigidbody>().velocity = Vector3.zero;
                        GetComponent<Rigidbody>().useGravity = false;
                        Speed = 0;
                        m_canMove = false;
                        transform.parent = _pb.transform;
                    }
                    break;
                case (SpellCollisionBehavior.SnapsToCenter):
                    if (enableDebugMessages)
                        Debug.Log("Spell [ID: " + name.Split('_')[1] + "] snaps to the center of players.");
                    if (!transform.parent || !transform.parent.GetComponent<PlayerBase>())
                    {
                        GetComponent<Rigidbody>().velocity = Vector3.zero;
                        GetComponent<Rigidbody>().useGravity = false;
                        Speed = 0;
                        m_canMove = false;
                        transform.parent = _pb.transform;
                        transform.position = _pb.transform.position;
                    }
                    break;
            }
        }
        /// <summary>
        /// Handles wall hit behavior
        /// </summary>
        /// <param name="_col"></param>
        private void HandleWallHit(GameObject _wall)
        {
            if (enableDebugMessages)
                Debug.Log("Spell [ID: " + name.Split('_')[1] + "] registered a wall hit!");
            
            // If we can't hit walls, return
            if (!Stats.WallBehavior.CanHit)
            {
                if (enableDebugMessages)
                    Debug.Log("Spell [ID: " + name.Split('_')[1] + "] does not hit walls.");

                return;
            }

            // If we deal damage to walls...
            if (Stats.WallBehavior.Damages)
            {
                // Get the build manager
                GameObject buildMan = GameObject.FindGameObjectWithTag("Build Manager");

                // If we got it...
                if (buildMan)
                {
                    // Get the build script
                    Build buildScript = buildMan.GetComponent<Build>();

                    // Get the health script of the wall we hit
                    BlockHealth blockHealthScript = _wall.transform.parent.parent.GetComponent<BlockHealth>();

                    // Derive any bonus damage towards walls
                    float _bonusDamage = Stats.WallBehavior.DealsBonusDamage ? (GetCurvedStat(Stats.WallBehavior.Graph_BonusDamage, Stats.WallBehavior.SP_BonusDamage)) : 0;

                    if (enableDebugMessages)
                        Debug.Log("Spell [ID: " + name.Split('_')[1] + "] is dealing " + (Damage + _bonusDamage).ToString() + " to this wall!");

                    // Add a health update to the build script (this is called over the network
                    buildScript.AddHealthUpdate(blockHealthScript.gridNode.index, Damage + _bonusDamage);

                    // Add a local block health update using the damage + bonus value
                    blockHealthScript.SetHealth(Damage + _bonusDamage);

                    // If we can hit repeatedly, add the collider to our damage timers list with a 0.5 second timer
                    if (Stats.CanHitRepeatedly && !m_damageTimers.ContainsKey(_wall))
                        m_damageTimers.Add(_wall, 0.5f);
                }
            }
            
            // Handle on-hit behaviors
            switch (Stats.WallBehavior.CollisionBehavior)
            {
                case (SpellCollisionBehavior.PassesThrough):
                    if (enableDebugMessages)
                        Debug.Log("Spell [ID: " + name.Split('_')[1] + "] passes through walls.");

                    break;
                case (SpellCollisionBehavior.DestroyedOnHit):
                    if (enableDebugMessages)
                        Debug.Log("Spell [ID: " + name.Split('_')[1] + "] is cached on contact.");
                    SourceWand.CacheSpellInstance(gameObject);
                    break;
                case (SpellCollisionBehavior.BouncesOff):
                    if (enableDebugMessages)
                        Debug.Log("Spell [ID: " + name.Split('_')[1] + "] bounces off of walls.");
                    GetComponent<Rigidbody>().velocity = Vector3.Reflect(GetComponent<Rigidbody>().velocity, (Vector3.Angle(transform.position - _wall.transform.position, transform.forward) > 90 ? _wall.transform.forward : -_wall.transform.forward ));
                    transform.LookAt(transform.position + GetComponent<Rigidbody>().velocity.normalized, Vector3.up);
                    break;
                case (SpellCollisionBehavior.HaltsAt):
                    if (enableDebugMessages)
                        Debug.Log("Spell [ID: " + name.Split('_')[1] + "] stops moving on contact with walls.");
                    GetComponent<Rigidbody>().velocity = Vector3.zero;
                    break;
                case (SpellCollisionBehavior.SticksTo):
                    if (enableDebugMessages)
                        Debug.Log("Spell [ID: " + name.Split('_')[1] + "] sticks to walls.");
                    if (!transform.parent || transform.parent.tag != "Building")
                    {
                        GetComponent<Rigidbody>().velocity = Vector3.zero;
                        GetComponent<Rigidbody>().useGravity = false;
                        Speed = 0;
                        m_canMove = false;
                        transform.parent = _wall.transform;
                    }
                    break;
                case (SpellCollisionBehavior.SnapsToCenter):
                    if (enableDebugMessages)
                        Debug.Log("Spell [ID: " + name.Split('_')[1] + "] snaps to the center of walls.");
                    if (!transform.parent || transform.parent.tag != "Building")
                    {
                        GetComponent<Rigidbody>().velocity = Vector3.zero;
                        GetComponent<Rigidbody>().useGravity = false;
                        Speed = 0;
                        m_canMove = false;
                        transform.parent = _wall.transform;
                        transform.position = _wall.transform.position;
                    }
                    break;
            }
        }
        /// <summary>
        /// Handles barrier hit behavior
        /// </summary>
        /// <param name="_col"></param>
        private void HandleSpellHit(SpellBase _bs)
        {
            if (enableDebugMessages)
                Debug.Log("Spell [ID: " + name.Split('_')[1] + "] registered a spell hit!");

            // If the base spell reference is null, return
            if (!_bs)
            {
                if (enableDebugMessages)
                    Debug.Log("Spell hit failed due to being passed a null base spell reference");
                return;
            }

            // If these spells are the same type, return
            if (PrimaryType == _bs.PrimaryType)
            {
                if (enableDebugMessages)
                    Debug.Log("Spell hit failed due to being the same primary type. Only barriers and non-barriers can interact.");
                return;
            }

            // If the other spell is not a barrier, return
            if (_bs.PrimaryType != SpellPrimaryType.Barrier)
            {
                if (enableDebugMessages)
                    Debug.Log("Spell hit failed because the spell hit was not a barrier.");
                return;
            }

            // If we cannot hit barriers, return
            if (!Stats.BarrierBehavior.CanHit)
            {
                if (enableDebugMessages)
                    Debug.Log("Spell hit failed due to this spell being unaffected by barriers");
                return;
            }

            if (Stats.BarrierBehavior.CollisionBehavior == SpellCollisionBehavior.DestroyedOnHit)
            {
                if (enableDebugMessages)
                    Debug.Log("Spell hit results in this spell being destroyed.");

                SourceWand.CacheSpellInstance(gameObject);
                return;
            }

            // If we can damage barriers and the barrier being hit belongs to the local player, deal damage based on our current and bonus damage
            if (Stats.BarrierBehavior.Damages && PlayerBase.GetLocalPlayer().photonView.ViewID == _bs.OwnerViewID)
            {
                if (enableDebugMessages)
                    Debug.Log("Spell hit results in damage dealt to the barrier.");

                float _bonusDamage = Stats.BarrierBehavior.DealsBonusDamage ? (GetCurvedStat(Stats.BarrierBehavior.Graph_BonusDamage, Stats.BarrierBehavior.SP_BonusDamage)) : 0;
                _bs.DamageSpell(Damage + _bonusDamage);

                // If we can hit repeatedly, add the collider to our damage timers list with a 0.5 second timer
                if (Stats.CanHitRepeatedly && !m_damageTimers.ContainsKey(_bs.gameObject))
                    m_damageTimers.Add(_bs.gameObject, 0.5f);
            }
            
            // Handle on-hit behaviors
            switch (Stats.BarrierBehavior.CollisionBehavior)
            {
                case (SpellCollisionBehavior.PassesThrough):
                    if (enableDebugMessages)
                        Debug.Log("Spell [ID: " + name.Split('_')[1] + "] passes through barriers.");
                    break;
                case (SpellCollisionBehavior.BouncesOff):
                    GetComponent<Rigidbody>().velocity = Vector3.Reflect(GetComponent<Rigidbody>().velocity, _bs.transform.forward);
                    transform.LookAt(transform.position + GetComponent<Rigidbody>().velocity.normalized, Vector3.up);
                    if (enableDebugMessages)
                        Debug.LogError("Spell [ID: " + name.Split('_')[1] + "] bounces off of barriers.");
                    break;
                case (SpellCollisionBehavior.HaltsAt):
                    if (enableDebugMessages)
                        Debug.Log("Spell [ID: " + name.Split('_')[1] + "] stops moving after hitting a barrier.");
                    GetComponent<Rigidbody>().velocity = Vector3.zero;
                    break;
                case (SpellCollisionBehavior.SticksTo):
                    if (enableDebugMessages)
                        Debug.Log("Spell [ID: " + name.Split('_')[1] + "] sticks to barriers.");
                    if (!transform.parent || !transform.parent.GetComponent<SpellBase>())
                    {
                        GetComponent<Rigidbody>().velocity = Vector3.zero;
                        GetComponent<Rigidbody>().useGravity = false;
                        Speed = 0;
                        m_canMove = false;
                        transform.parent = _bs.transform;
                    }
                    break;
                case (SpellCollisionBehavior.SnapsToCenter):
                    if (enableDebugMessages)
                        Debug.Log("Spell [ID: " + name.Split('_')[1] + "] snaps to the center of barriers.");
                    if (!transform.parent || !transform.parent.GetComponent<SpellBase>())
                    {
                        GetComponent<Rigidbody>().velocity = Vector3.zero;
                        GetComponent<Rigidbody>().useGravity = false;
                        Speed = 0;
                        m_canMove = false;
                        transform.parent = _bs.transform;
                        transform.position = _bs.transform.position;
                    }
                    break;
            }
        }
        /// <summary>
        /// Handles flight distance and lifetime tracking and behaviors
        /// </summary>
        private void HandleFlightTracking()
        {
            // CHECK LIFESPAN //
            #region
            // If the lifespan is limited...
            if (Stats.LimitLifespan)
            {
                // Increase the amount of time that the spell has been active
                FlightTime += Time.fixedDeltaTime;

                // If our flight time exceeds our maximum...
                if (FlightTime > m_lifetime)
                    switch (Stats.CacheAtLifetime)
                    {
                        // If we cache this spell when its lifetime exceeds the set amount, call the source wand to do so.
                        case (true):
                            if (SourceWand)
                            {
                                if (enableDebugMessages)
                                    Debug.Log("Caching Spell Instance because its lifetime has expired at " + FlightTime.ToString() + " out of " + m_lifetime.ToString() + " seconds.");

                                FlightTime = 0;

                                SourceWand.CacheSpellInstance(gameObject);
                            }
                            else
                                DestroySpell();
                            return;
                        // If not, we simply stop it.
                        case (false):
                            GetComponent<Rigidbody>().velocity = Vector3.zero;
                            break;
                    }
            }
            #endregion

            // CHECK RANGE //
            #region
            // If we limit the range of the spell...
            if (Stats.LimitRange)
            {
                // Increase the distance travelled by the amount of time this fixed update multiplied by the velocity
                // Spells without a velocity do not have distance tracking, as they are likely kinematically controlled
                DistanceTravelled += Time.fixedDeltaTime * GetComponent<Rigidbody>().velocity.magnitude;

                // If our flight distance exceeds our maximum...
                if (DistanceTravelled > m_range)
                    switch (Stats.CacheAtRange)
                    {
                        // If we cache this spell when its range exceeds the set amount, call the source wand to do so.
                        case (true):
                            if (enableDebugMessages)
                                Debug.Log("Caching Spell Instance due to range constraints");

                            DistanceTravelled = 0;

                            SourceWand.CacheSpellInstance(gameObject);
                            return;
                        // If not, we simply stop it.
                        case (false):
                            GetComponent<Rigidbody>().velocity = Vector3.zero;
                            break;
                    }
            }
            #endregion
        }
        /// <summary>
        /// Handles mid-air traversal movement
        /// </summary>
        private void HandleFlightTravel()
        {
            // We only handle flight behaviors if we are not on the ground
            // We also check here if we have movement disabled
            if (m_onGround || !m_canMove)
            {
                if (enableDebugMessages)
                    Debug.Log("Spell [ID: " + name.Split('_')[1] + "] is either on the ground or unable to move!");

                return;
            }

            // if (enableDebugMessages)
                // Debug.Log("Spell [ID: " + name.Split('_')[1] + "] handling flight behavior. Speed: " + Speed.ToString() + ", Vector: " + GetComponent<Rigidbody>().velocity.magnitude.ToString());

            if (Stats.GravityFactor > 0 && Vector3.Angle(transform.forward, Vector3.down) > 15)
                transform.rotation *= Quaternion.AngleAxis(Stats.GravityFactor * Time.fixedDeltaTime, transform.right);

            // Update the rigidbody's velocity to match the current direction, but at our derived speed
            GetComponent<Rigidbody>().velocity = transform.forward * Speed;

            transform.LookAt(transform.position + GetComponent<Rigidbody>().velocity, Vector3.up);
        }
        /// <summary>
        /// Handles terrain traversal movement 
        /// </summary>
        private void HandleTerrainTravel()
        {
            // ABORT CIRCUMSTANCES //
            #region
            // If terrain destroys the spell or we are not set to travel over terrain, return
            if (Stats.DestroyedByTerrain || !Stats.TravelOverTerrain || !m_canMove)
                return;
            #endregion

            // CREATE RAYCAST DATA //
            #region
            // Get the amount of distance that, given the spell's current speed, it should travel
            float _distThisUpdate = GetComponent<Rigidbody>().velocity.magnitude * Time.fixedDeltaTime;

            // Create a hit result
            RaycastHit hit;

            // Create a starting position
            Vector3 _startPos;
            
            // Create a directional vector
            Vector3 _dir;

            // Because RaycastHit is a struct
            bool gotHit = false;

            // Create a mask that only detects default (terrain) and environmental (prefab) objects
            LayerMask _mask = LayerMask.GetMask("Default", "Environment");
            #endregion

            // FRONT CHECK //
            #region
            // Start positon is slightly above center so that we don't accidentally clip the floor
            _startPos = transform.position + (transform.up * 0.1f);

            // We'll be raycasting forward
            _dir = transform.forward;

            // Show where we raytrace
            Debug.DrawLine(_startPos, _startPos + _dir * _distThisUpdate, Color.red);

            // Is something in front of us?
            gotHit = Physics.Raycast(_startPos, _dir, out hit, _distThisUpdate, _mask);
            
            /*  Archived behavior: causes the spell to be destroyed if the angle of the hit exceeds a certain threshold.
            // If we got a hit and the angle of the hit is over 90 degrees, cache the spell
            if (gotHit && hit.transform != transform)
                if (Vector3.Angle(transform.forward, hit.normal) > 170)
                    SourceWand.CacheSpellInstance(gameObject);
            */
            #endregion

            // DOWN CHECK //
            #region
            // We only change the direction for this check, going down
            _dir = -transform.up;

            // If not, below us?
            if (!gotHit)
                gotHit = Physics.Raycast(_startPos, _dir, out hit, 0.7f, _mask);
            #endregion

            // DOWN BACK CHECK //
            #region
            // The starting position is slightly in front and slightly below. We are checking for a "cliff" face below us
            _startPos = transform.position + (transform.forward * 0.1f) + (-transform.up * 0.1f);

            // Direction is backwards
            _dir = -transform.forward;

            // Show where we raytrace
            Debug.DrawLine(_startPos, _startPos + _dir * 0.2f, Color.green);

            // If not, below AND behind us?
            if (!gotHit)
                // If we hit something below and behind us
                gotHit = Physics.Raycast(_startPos, _dir, out hit, 0.2f, _mask);
            #endregion

            // HANDLE HIT BEHAVIORS
            #region
            // If we hit anything...
            if (gotHit)
            {
                // If we were previously in the air...
                if (!m_onGround)
                {
                    if (enableDebugMessages)
                        Debug.Log("Spell has found ground on " + hit.transform.name);

                    // Toggle the grounded state
                    m_onGround = true;

                    // Zero out our vertical velocity
                    m_vertVel = 0;
                }

                // Get the cross product of the right side of our spell and the surface we hit
                // This results in what is "forward" if we had the same "right", but the surface was the floor
                Vector3 lookAt = Vector3.Cross(transform.right, hit.normal);
                
                // Look in the direction of the new "forward" given the surface as the floor
                transform.LookAt(transform.position + lookAt, hit.normal);

                // Change our velocity to the new forward times our current speed
                GetComponent<Rigidbody>().velocity = transform.forward * Speed;

                // Set our position to the hit point
                transform.position = hit.point;

            }
            // Otherwise, handle falling/floating behaviors
            else
            {
                // If we were previously on the ground, toggle the state
                if (m_onGround)
                    m_onGround = false;

                // Increment the vertical velocity by our gravity factor during this fixed update
                m_vertVel += Stats.GravityFactor * Time.fixedDeltaTime;

                // Set the velocity to the forward direction of the spell, plus the WORLD down multiplied by the vertical speed.
                GetComponent<Rigidbody>().velocity = ((transform.forward * Speed + Vector3.down * m_vertVel) / 2).normalized * Speed;

                // Look in the direction of the new "forward" given the surface as the floor
                transform.LookAt(transform.position + GetComponent<Rigidbody>().velocity.normalized, Vector3.up);
            }
            #endregion
        }
        /// <summary>
        /// Handles spell movement when the spell has a target
        /// </summary>
        private void HandleTargetTracking()
        {
            // ABORT CIRCUMSTANCES //
            #region
            // DEVELOPER NOTE - Terrain travel behaviors conflict with targetting navigation and vice versa. This is disabled until a solution can be found.
            if (Stats.TravelOverTerrain)
                return;

            // If we have no targetting behavior, or we have no target, return
            if (Stats.TargettingBehavior == SpellTargettingBehavior.None || !Target)
                return;
            #endregion

            // DERIVE TO-TARGET ROTATION //
            #region
            // Get the directional vector from our current spell's position to the target
            Vector3 _dirToTarget = (Target.transform.position - transform.position).normalized;

            // If we are pointing towards it already, return
            if (transform.forward == _dirToTarget)
                return;

            // Get the rotation that would make this spell face towards the target
            Quaternion _toTargetRotation = Quaternion.FromToRotation(transform.forward, _dirToTarget);
            #endregion

            // APPLY TO-TARGET ROTATION //
            #region
            // Set the rotation to be a spherical linearly interpolated (slerped) value from the current rotation to a percentage of the final one by our tracking rate
            transform.rotation = Quaternion.Slerp(transform.rotation, _toTargetRotation, GetCurvedStat(Stats.Graph_BaseTrackingRate, Stats.SP_TrackingRate));

            // Update the velocity to be the current speed times the forward 
            GetComponent<Rigidbody>().velocity = GetComponent<Rigidbody>().velocity.magnitude * transform.forward;
            #endregion
        }
        /// <summary>
        /// Handles updating dynamic values via the lifetime of the spell
        /// </summary>
        private void HandleDynamicValues()
        {
            // DERIVE SCALING PERCENTAGE //
            #region
            float _percScale = 0;

            if (Stats.LimitLifespan || Stats.LimitRange)
            {
                // If we limit both range and lifespan, we scale by the smaller of the two stats
                if (Stats.LimitLifespan && Stats.LimitRange)
                {
                    float _percThroughLifespan = Mathf.Clamp01(FlightTime / m_lifetime);
                    float _percThroughRange = Mathf.Clamp01(DistanceTravelled / m_range);

                    _percScale = _percThroughLifespan >= _percThroughRange ? _percThroughLifespan : _percThroughRange;
                }
                // If we only use lifespan, use it
                else if (Stats.LimitLifespan)
                    _percScale = Mathf.Clamp01(FlightTime / m_lifetime);
                // If we only use range, use it
                else
                    _percScale = Mathf.Clamp01(DistanceTravelled / m_range);
            }
            #endregion
            
            // EVALUATE DYNAMIC VALUES //
            #region
            // Evaluate the new damage value
            Damage = m_baseDamage * Stats.Graph_DamageOverTime.Evaluate(_percScale);

            // Evaluate the new push force
            PushForce = m_basePushForce * Stats.Graph_PushForceOverTime.Evaluate(_percScale);

            // Evaluate the new size
            Size = m_baseSize * Stats.Graph_SizeOverTime.Evaluate(_percScale);

            // Evaluate the new speed
            Speed = m_baseSpeed * Stats.Graph_SpeedOverTime.Evaluate(_percScale);

            // Evaluate the new tracking rate
            TrackingRate = m_baseTrackingRate * Stats.Graph_TrackingRateOverTime.Evaluate(_percScale);
            #endregion

            // APPLY DYNAMIC STATS //
            #region
            // Log the spell's current parent
            Transform _parent = transform.parent;

            // Unparent the spell instance from the parent
            transform.parent = null;

            // Update instance scale
            transform.localScale = Vector3.one * Size;

            // if (enableDebugMessages)
                // Debug.Log("Spell [Wand: " + name.Split('_')[0] + " ID: " + name.Split('_')[1] + "] size set to " + Size);

            // Reset parent to the previous parent
            transform.parent = _parent;

            List<TrailRenderer> _trails = new List<TrailRenderer>();

            foreach (TrailRenderer _trail in Color1Trails)
                if (_trail)
                    _trails.Add(_trail);
            foreach (TrailRenderer _trail in Color2Trails)
                if (_trail)
                    _trails.Add(_trail);
            foreach (TrailRenderer _trail in Color3Trails)
                if (_trail)
                    _trails.Add(_trail);
            foreach (TrailRenderer _trail in Color4Trails)
                if (_trail)
                    _trails.Add(_trail);
            foreach (TrailRenderer _trail in _trails)
                _trail.widthMultiplier = Size / 2;

            // Update instance velocity
            GetComponent<Rigidbody>().velocity = transform.forward * Speed;

            // if (enableDebugMessages)
                // Debug.Log("Updating spell size to " + transform.localScale.ToString() + " scale and " + GetComponent<Rigidbody>().velocity.magnitude.ToString() + " speed.");

            #endregion
        }
        /// <summary>
        /// Handles updating Damage Over Time behaviors
        /// </summary>
        private void HandleDOTs()
	    {
		    if (m_damageTimers == null)
		    	m_damageTimers = new Dictionary<GameObject, float>();
        	
		    if (m_damageTimers.Count < 1)
			    return;
        	
            foreach (GameObject key in m_timedObjects)
            {
                m_damageTimers[key] -= Time.fixedDeltaTime;

                if (m_damageTimers[key] <= 0)
                {
                    // If a player was hit, handle it
                    if (key.GetComponent<PlayerBase>())
                        HandlePlayerHit(key.GetComponent<PlayerBase>());
                    // If a wall was hit, handle it
                    else if (key.transform.parent.parent.gameObject.GetComponent<BlockHealth>())
                        HandleWallHit(key);
                    // If a spell was hit, handle it
                    else if (key.GetComponent<SpellBase>())
                        HandleSpellHit(key.GetComponent<SpellBase>());

                    // Increase the damage timer for this object by 0.5 seconds
                    m_damageTimers[key] += 0.5f;
                }
            }
        }
        #endregion

        // GETTER METHODS //
        #region
        /// <summary>
        /// Returns the value of a given spell cast by its stat.
        /// </summary>
        /// <param name="_accuracy"></param>
        /// <returns></returns>
        private float GetCurvedStat(AnimationCurve _statCurve, StatScalingParam _param)
        {
            // Create a value field at the base value by default
            float _value = _statCurve.Evaluate(0);

            // If there is no scaling in the accuracy stat, return the base value
            if (_param == StatScalingParam.NoScaling)
                return _value;

            // Differentiate our scaled behavior by the scaling parameter
            switch (_param)
            {
                // If we scale by rarity...
                case (StatScalingParam.Rarity):
                    // Get the stat by the wand's rarity divided by the number of rarities (minus one)
                    _value = _statCurve.Evaluate((int)Rarity / (Enum.GetValues(typeof(Rarity)).Length - 1));
                    break;
                // If we scale by mana...
                case (StatScalingParam.ManaCharged):
                    // If we have a charging behavior, use the amount of mana we have charged
                    if (Stats.ChargingBehavior != ChargingBehavior.None && Stats.ChargingBehavior != ChargingBehavior.FireAtMin)
                        _value = _statCurve.Evaluate((int)ManaCharge / Stats.MaxChargeValue);
                    // Otherwise, evaluate the mana cost value divided by the total possible mana pool
                    else
                        _value = _statCurve.Evaluate(Stats.Graph_ManaCost.Evaluate((int)Rarity / (Enum.GetValues(typeof(Rarity)).Length - 1)) / 100);
                    break;
            }

            // Return the accuracy
            return _value;
        }
        #endregion

        // EXECUTABLE METHODS //
        #region
        /// <summary>
        /// Ensure that the spell is able to retrieve its spell data from the catalog
        /// </summary>
        private void ValidateSpell()
        {
            // If we have no catalog, destroy the spell
            if (!SpellCatalog.GetCatalog())
                DestroySpell();

            if (!SpellType.IsValidType((int)PrimaryType, (int)SecondaryType))
                DestroySpell();

            // If our catalog has no spells of the spell's type, destroy the spell
            if (!SpellCatalog.GetCatalog().ContainsSpellOfType(PrimaryType, SecondaryType))
                DestroySpell();
            // Otherwise, get the name and stats from the catalog
            else
            {
                Spell _thisSpell = SpellCatalog.GetCatalog().GetSpellOfType(PrimaryType, SecondaryType);
                name = _thisSpell.Name;
                Stats = _thisSpell.Stats;
                TravelSound = _thisSpell.spellTravel;
            }

            // If we couldn't get stats, destroy the spell
            if (!Stats)
                DestroySpell();
        }
        /// <summary>
        /// Sets the wands default stats given its rarity and starting mana charge.
        /// </summary>
        public void InitializeSpell()
        {
            if (!Stats)
                Stats = SpellCatalog.GetCatalog().GetSpellOfType(PrimaryType, SecondaryType).Stats;

            if (!Stats)
            {
                Debug.LogError("No stats!");
                return;
            }

            // SET BASE STATS //
            #region
            m_baseDamage = GetCurvedStat(Stats.Graph_BaseDamage, Stats.SP_BaseDamage);
            m_basePushForce = GetCurvedStat(Stats.Graph_BasePushForce, Stats.SP_PushForce);
            m_baseSpeed = GetCurvedStat(Stats.Graph_BaseSpeed, Stats.SP_BaseSpeed);
            m_baseSize = GetCurvedStat(Stats.Graph_BaseSize, Stats.SP_BaseSize);
            m_lifetime = GetCurvedStat(Stats.Graph_Lifetime, Stats.SP_Lifetime);
            m_range = GetCurvedStat(Stats.Graph_Range, Stats.SP_Range);
            m_baseTrackingRate = GetCurvedStat(Stats.Graph_BaseTrackingRate, Stats.SP_TrackingRate);
            m_damageTimers = new Dictionary<GameObject, float>();

            if (enableDebugMessages)
                Debug.Log("Spell was initialized with the following stats: Damage: " + m_baseDamage.ToString() + " | Push Force: " + m_basePushForce.ToString() + " | Speed: " + m_baseSpeed.ToString() + " | Size: " + m_baseSize.ToString() + " | Lifetime: " + m_lifetime.ToString() + " | Range: " + m_range.ToString() + " | Tracking Rate: " + m_baseTrackingRate.ToString());
            #endregion

            // SET PHYSICS STATS //
            #region
            // Assign whether or not we use gravity and alter the mass to match the factor
            GetComponent<Rigidbody>().useGravity = Stats.GravityFactor == 0 ? false : true;
            GetComponent<Rigidbody>().mass = Stats.GravityFactor == 0 ? 1 : Stats.GravityFactor;
            #endregion

            // START AUDIO //
            #region
            ResetSpellAudio();
            #endregion

            // TOGGLE INSTANCING ON WEBGL //
            #region
            ToggleAllGPUInstancing(transform, Application.platform != RuntimePlatform.WebGLPlayer);

            if (Application.platform != RuntimePlatform.WebGLPlayer)
                UpdateColors();
            #endregion

            m_initialized = true;
        }
        /// <summary>
        /// Disables GPU instancing on spell materials at runtime on WebGL platform.
        /// </summary>
        /// <param name="parent"></param>
        void ToggleAllGPUInstancing(Transform parent, bool _state)
        {
            foreach (Transform child in parent)
            {
                if (child.GetComponent<Renderer>())
                    if (child.GetComponent<Renderer>().materials.Length > 0)
                        foreach (Material _mat in child.GetComponent<Renderer>().materials)
                            _mat.enableInstancing = _state;

                ToggleAllGPUInstancing(child, _state);
            }
        }
        /// <summary>
        /// Resets the spell to its base state
        /// </summary>
        public void ResetInstance()
        {
            // RESET TRAILS //
            #region
            foreach (TrailRenderer _trail in Color1Trails)
                _trail.Clear();

            foreach (TrailRenderer _trail in Color2Trails)
                _trail.Clear();

            foreach (TrailRenderer _trail in Color3Trails)
                _trail.Clear();

            foreach (TrailRenderer _trail in Color4Trails)
                _trail.Clear();
            #endregion

            // RESET DYNAMIC STATS //
            #region
            Damage = m_baseDamage;
            PushForce = m_basePushForce;
            Speed = m_baseSpeed;
            Size = m_baseSize;
            TrackingRate = m_baseTrackingRate;
            DistanceTravelled = 0;
            FlightTime = 0;
            m_vertVel = 0;
            m_damageTimers = new Dictionary<GameObject, float>();
            #endregion

            // RESET AUDIO //
            #region
            ResetSpellAudio();
            #endregion
        }
        /// <summary>
        /// Restarts the spell's travel sound to the start, if there is any
        /// </summary>
        private void ResetSpellAudio()
        {
            if (TravelSound)
            {
                if (!GetComponent<AudioSource>())
                    gameObject.AddComponent<AudioSource>();

                if (!aSource)
                {
                    aSource = GetComponent<AudioSource>();
                    aSource.clip = TravelSound;
                    aSource.loop = false;
                }

                aSource.Stop();
                aSource.Play();
            }
        }
        /// <summary>
        /// Updates the colors of each renderer loaded into the component lists
        /// </summary>
        /// <param name="_spellPrimary"></param>
        /// <param name="_spellSecondary"></param>
        /// <param name="_particlePrimary"></param>
        /// <param name="_particleSecondary"></param>
        private void UpdateColors()
        {
            // Enforces color alpha values (No transparent spells!)
            SpellColor1.a = 1;
            SpellColor2.a = 1;
            SpellColor3.a = 1;
            SpellColor4.a = 1;


            if (Color1Meshes.Length > 0)
                foreach (MeshRenderer renderer in Color1Meshes)
                {
                    if (renderer.material.shader.name == "Standard")
                        renderer.material.SetColor("_EmissionColor", SpellColor1);
                    else if (renderer.material.shader.name == "Particles/Additive")
                        renderer.material.SetColor("_TintColor", SpellColor1);
                    else
                        renderer.material.color = SpellColor1;
                }

            if (Color2Meshes.Length > 0)
                foreach (MeshRenderer renderer in Color2Meshes)
                {
                    if (renderer.material.shader.name == "Standard")
                        renderer.material.SetColor("_EmissionColor", SpellColor2);
                    else if (renderer.material.shader.name == "Particles/Additive")
                        renderer.material.SetColor("_TintColor", SpellColor2);
                    else
                        renderer.material.color = SpellColor2;
                }

            if (Color3Meshes.Length > 0)
                foreach (MeshRenderer renderer in Color3Meshes)
                {
                    if (renderer.material.shader.name == "Standard")
                        renderer.material.SetColor("_EmissionColor", SpellColor3);
                    else if (renderer.material.shader.name == "Particles/Additive")
                        renderer.material.SetColor("_TintColor", SpellColor3);
                    else
                        renderer.material.color = SpellColor3;
                }

            if (Color4Meshes.Length > 0)
                foreach (MeshRenderer renderer in Color4Meshes)
                {
                    if (renderer.material.shader.name == "Standard")
                        renderer.material.SetColor("_EmissionColor", SpellColor4);
                    else if (renderer.material.shader.name == "Particles/Additive")
                        renderer.material.SetColor("_TintColor", SpellColor4);
                    else
                        renderer.material.color = SpellColor4;
                }

            if (Color1Particles.Length > 0)
                foreach (ParticleSystemRenderer renderer in Color1Particles)
                {
                    var _main = renderer.gameObject.GetComponent<ParticleSystem>().main;
                    _main.startColor = SpellColor1;
                }

            if (Color2Particles.Length > 0)
                foreach (ParticleSystemRenderer renderer in Color2Particles)
                {
                    var _main = renderer.gameObject.GetComponent<ParticleSystem>().main;
                    _main.startColor = SpellColor2;
                }

            if (Color3Particles.Length > 0)
                foreach (ParticleSystemRenderer renderer in Color3Particles)
                {
                    var _main = renderer.gameObject.GetComponent<ParticleSystem>().main;
                    _main.startColor = SpellColor3;
                }

            if (Color4Particles.Length > 0)
                foreach (ParticleSystemRenderer renderer in Color4Particles)
                {
                    var _main = renderer.gameObject.GetComponent<ParticleSystem>().main;
                    _main.startColor = SpellColor4;
                }

            if (Color1Trails.Length > 0)
                foreach (TrailRenderer renderer in Color1Trails)
                {
                    renderer.startColor = SpellColor1;
                    renderer.endColor = new Color(SpellColor1.r, SpellColor1.g, SpellColor1.b, 0);
                }

            if (Color2Trails.Length > 0)
                foreach (TrailRenderer renderer in Color2Trails)
                {
                    renderer.startColor = SpellColor2;
                    renderer.endColor = new Color(SpellColor2.r, SpellColor2.g, SpellColor2.b, 0);
                }

            if (Color3Trails.Length > 0)
                foreach (TrailRenderer renderer in Color3Trails)
                {
                    renderer.startColor = SpellColor3;
                    renderer.endColor = new Color(SpellColor3.r, SpellColor3.g, SpellColor3.b, 0);
                }

            if (Color4Trails.Length > 0)
                foreach (TrailRenderer renderer in Color4Trails)
                {
                    renderer.startColor = SpellColor4;
                    renderer.endColor = new Color(SpellColor4.r, SpellColor4.g, SpellColor4.b, 0);
                }
        }
        /// <summary>
        /// Depletes a spell's stashed mana by a given amount. If the stashed mana falls below zero, the spell is destroyed.
        /// </summary>
        /// <param name="_damage"></param>
        public void DamageSpell(float _damage)
        {
            // Decrease the spell's mana charge by the given amount
            ManaCharge -= _damage;

            // If the mana charge is at or below zero...
            if (ManaCharge <= 0)
            {
                // Clamp the charge value to zero
                ManaCharge = 0;

                // Cache if we have a source wand, otherwise destroy the spell
                if (SourceWand)
                    SourceWand.CacheSpellInstance(gameObject);
                else
                    DestroySpell();
            }
        }
        /// <summary>
        /// Destroys the spell instance.
        /// </summary>
        public void DestroySpell()
        {
            // If we are testing, abort the destroy behavior!
            if (forcePersistForTesting)
                return;

            Destroy(gameObject);
        }
        #endregion

        // SETTER METHODS //
        #region
        /// <summary>
        /// Sets the default spell data for this spell instance
        /// </summary>
        /// <param name="_stats"></param>
        public void SetSpellData(SpellStats _stats)
        {
            if (!_stats)
                return;

            Stats = _stats;
        }
        /// <summary>
        /// Sets the spell's owner ID to the given integer value
        /// </summary>
        /// <param name="_ownerViewID"></param>
        public void SetOwnerViewID(int _ownerViewID)
        {
            OwnerViewID = _ownerViewID;
        }
        /// <summary>
        /// Sets the target to a player gameobject with the given view ID
        /// </summary>
        /// <param name="_targetViewID"></param>
        public void SetTargetViewID(int _targetViewID)
        {
            GameObject[] _players = GameObject.FindGameObjectsWithTag("Player");

            // Set the target to the first player which has a view ID that matches the given Photon ID
            for (int i = 0; i < _players.Length; i++)
                if (_players[i].GetComponent<PlayerBase>())
                    if (_players[i].GetComponent<PlayerBase>().photonView.ViewID == _targetViewID)
                        Target = _players[i];
        }
        /// <summary>
        /// Sets the source of the spell
        /// </summary>
        /// <param name="_sourceWand"></param>
        public void SetSourceWand(NewWandScript _sourceWand)
        {
            // Already have one? return.
            if (SourceWand)
                return;

            // If we somehow got a null reference, return
            if (!_sourceWand)
                return;

            // Otherwise, set the reference.
            SourceWand = _sourceWand;
            
            // Set the base colors of the spell to the wand's
            SetBaseColors(SourceWand.SpellColor1, SourceWand.SpellColor2, SourceWand.SpellColor3, SourceWand.SpellColor4);
        }
        /// <summary>
        /// Sets the default color parameters for the spell and updates the materials
        /// </summary>
        /// <param name="_color1"></param>
        /// <param name="_color2"></param>
        /// <param name="_color3"></param>
        /// <param name="_color4"></param>
        public void SetBaseColors(Color _color1, Color _color2, Color _color3, Color _color4)
        {
            SpellColor1 = _color1;
            SpellColor2 = _color2;
            SpellColor3 = _color3;
            SpellColor4 = _color4;

            UpdateColors();
        }
        #endregion
        
        // PHOTON METHODS //
        //#region
        //public static object Deserialize(byte[] data)
        //{
        //    var result = new BaseSpell();

        //    result.OwnerViewID = data[0];
        //    result.PrimaryType = (SpellPrimaryType)data[1];
        //    result.SecondaryType = (SpellSecondaryType)data[2];
        //    result.Damage = data[3];
        //    result.Speed = data[4];
        //    result.Size = data[5];

        //    return result;
        //}
        //public static byte[] Serialize(object customType)
        //{
        //    var c = (BaseSpell)customType;
            
        //    return new byte[] {
        //        (byte)c.OwnerViewID,
        //        (byte)(int)c.PrimaryType,
        //        (byte)(int)c.SecondaryType,
        //        (byte)c.Damage,
        //        (byte)c.Speed,
        //        (byte)c.Size
        //};
        //}
        //#endregion
    }
}
