/*------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        *  Spawns a new set of grenades. networkSpawn allows us to determine if the server is spawning the grenades and if it's a local spawn, we'll tell the server we've fired grenades
        *  ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------*/
        public static void CreateGrenade(ShipRefs ship)
        {
            // if we're a player then play a 2D deployment sound
            if (ship.IsPlayer)
            {
                AudioHelpers.PlayOneShot(AudioHelpers.GetAudioClip(AudioHelpers.Weapons_MineDrop), AudioHelpers.E_AUDIOCHANNEL.SFX, 1.0f, 1.0f);
            }

            // create the grenades
            GrenadeObject.CreateNew(ship.transform.TransformPoint(-GrenadeSpawnSpread, 0.0f, ship.MeshBoundsFront.z), ship.RBody.rotation, ship);
            GrenadeObject.CreateNew(ship.transform.TransformPoint(0.0f, 0.0f, 1.0f), ship.RBody.rotation, ship);
            GrenadeObject.CreateNew(ship.transform.TransformPoint(GrenadeSpawnSpread, 0.0f, ship.MeshBoundsFront.z), ship.RBody.rotation, ship);
        }
        /* This is called every time a ship passes the mid line. r is the reference to the ship that has passed. */
        public override void OnShipTriggerMidLine(ShipRefs r)
        {
            // validate the lap so it can be updated next time the player triggers the start line
            r.LapValidated  = true;
            r.MiddleSection = r.CurrentSection;

            if (r.IsPlayer && !r.PassedValidationGate)
            {
                AudioHelpers.PlayOneShot(AudioHelpers.UI_Checkpoint, AudioHelpers.E_AUDIOCHANNEL.INTERFACE, 1.0f, 1.0f);
                CalculateAndDisplayRelativeTime(r);

                r.PassedValidationGate = true;
            }
        }
        /*-----------------------------------------------------------------------------------------------------------------------
         * Assuming the prefab is setup correctly with the environment collider being solid and the ship collider being a trigger,
         *      this will trigger when the environment collider starts to collide with something.
         * ----------------------------------------------------------------------------------------------------------------------*/
        private void OnCollisionEnter(Collision other)
        {
            int otherLayer = other.gameObject.layer;

            /*------------------------------------------------------
             * Determine if we've hit the track or floor.
             * We could also determine if we hit a wall by using:
             *
             * bool hitwall = otherLayer == LayerIDs.TrackWall;
             * -----------------------------------------------------*/
            bool hitFloor = otherLayer == LayerIDs.SmoothFloor || other.gameObject.layer == LayerIDs.FakeFloor || other.gameObject.layer == LayerIDs.TrackFloor;

            // reflect the grenade
            if (hitFloor)
            {
                Vector3 bounceNormal = other.contacts[0].normal;

                // reflect the forward vector of the grenade
                Vector3 forward = transform.forward;
                forward           = Vector3.Reflect(forward, bounceNormal);
                transform.forward = forward;

                Body.AddForce(bounceNormal * StatBounceForce, ForceMode.VelocityChange);
                StatBounceForce *= 0.9f;

                // play Impact Sound
                AudioHelpers.PlayOneShot(AudioHelpers.GetAudioClip(AudioHelpers.Ship_FloorHit), AudioHelpers.E_AUDIOCHANNEL.SFX, 0.4f, 1.0f, transform.position, null, 15.0f, 30.0f);

                if (BounceCount > StatMaxBounces)
                {
                    DestroyProjectile(null);
                }

                // temporary bounce count immunity
                if (Lifetime > 0.5f)
                {
                    ++BounceCount;
                }
            }
            else
            {
                DestroyProjectile(null);
            }
        }
        /* This is called every time a ship passes the start line, r is the reference to the ship that has passed. */
        public override void OnShipTriggerStartLine(ShipRefs r)
        {
            /* If this lap has been validated or the ship hasn't done any laps yet, then we want to do some stuff. */
            if (r.LapValidated || r.CurrentLap == 0)
            {
                // invalidate the lap again
                r.LapValidated = false;

                /* If the ship has finished a lap and is not on the last lap then we want to store and display some information. */
                if (r.CurrentLap > 0 && r.CurrentLap <= Race.MaxLaps)
                {
                    if (r.IsPlayer)
                    {
                        /* Update best time */
                        if ((r.CurrentLapTime < r.BestLapTime || !r.HasBestLapTime) && !r.LoadedBestLapTime)
                        {
                            r.BestLapTime    = r.CurrentLapTime;
                            r.HasBestLapTime = true;
                        }

                        /* Perfect lap notification */
                        if (r.IsPerfectLap)
                        {
                            // this triggers an onscreen message to appear. You can provide a color or you can write it using richtext in the string.
                            BallisticEvents.Ui.CallOnTriggerMessage("PERFECT LAP", r, ScriptableHud.BnGAccent);

                            // this plays a voice, you can feed this any sound you want (if you load your own you can also use that).
                            AudioHelpers.PlayVoice(AudioHelpers.Voice_PerfectLap);
                        }

                        /* Interface sounds */
                        if (r.CurrentLap == Race.MaxLaps - 1)
                        {
                            BallisticEvents.Ui.CallOnTriggerMessage("FINAL LAP", r, ScriptableHud.BnGAccent);
                            AudioHelpers.PlayVoice(AudioHelpers.Voice_FinalLap);
                        }
                        AudioHelpers.PlayOneShot(AudioHelpers.UI_Checkpoint, AudioHelpers.E_AUDIOCHANNEL.INTERFACE, 1.0f, 1.0f);
                    }

                    // set values for current lap
                    r.LapTimes[r.CurrentLap - 1]    = r.CurrentLapTime;
                    r.PerfectLaps[r.CurrentLap - 1] = r.IsPerfectLap;
                }

                /* Tasks for when the ship has completed the race */
                if (r.CurrentLap >= Race.MaxLaps && !r.FinishedEvent && !r.Eliminated)
                {
                    r.FinishedEvent = true;

                    // calling this does some needed config to mark the ship as having finished
                    RaceHelpers.FinishRace(r);

                    // if this is a player ship then set the ship as an AI ship and then save the time
                    if (r.IsPlayer)
                    {
                        // destroy the ship camera and replace it with the finished camera
                        Object.Destroy(r.ShipCamera.GetComponent <ShipCamera>());
                        ShipFCam finishCam = r.ShipCamera.gameObject.AddComponent <ShipFCam>();
                        finishCam.r = r;

                        r.IsAi = true;
                        SaveTime(r);
                    }
                }

                /* Reset timers and states */
                // this resets the current laps time to zero
                r.CurrentLapTime = 0.0f;
                r.IsPerfectLap   = true;
                ++r.CurrentLap;
                r.PassedValidationGate = false;

                // this clears the hit weapon pads so ships can use them again
                r.ClearHitPads();
                BallisticEvents.Race.CallOnShipLapUpdate(r);

                // this calculates the time between this ship and another (depending on the ships position)
                if (r.IsPlayer)
                {
                    CalculateAndDisplayRelativeTime(r);
                }

                // give the ship a turbo
                PickupRegistry.GivePickupToShip(r, PickupRegistry.FindPickupByName("turbo"));
            }
        }