/*
	Rogue Trader.
	Copyright (c) 2007, Darragh Coy.
	All rights reserved.
	Original work(s), source code & author(s) details available at: http://www.darraghcoy.com

	Redistribution and use in source and binary forms, with or without modification, are permitted 
	provided that the following conditions are met:

		* 	Redistributions of source code must retain the above copyright notice, this list of 
			conditions and the following disclaimer.
		* 	Redistributions in binary form must reproduce the above copyright notice, this list of 
			conditions and the following disclaimer in the documentation.
		* 	Neither the name of the author nor the names of contributors may be used to endorse or 
			promote products derived from this software without specific prior written permission.
		* 	The use of any third party libraries, modules or data files provided with the source 
			form of the program must follow and respect the individual licensing terms of these 
			components. You do not have permission to modify or circumvent the licensing terms for 
			provided third party components unless the third party licenses specifically allow this. 
			If you do not agree to the terms of provided third party components then you do not have 
			permission to use them in your derived work(s).
		* 	The terms of this license do not apply to separate (but related) distributions if supplied 
			by the author(s), only to the contents of this source distribution, derived works, and the 
			binaries produced by either modified or unmodified versions of the source code. The terms 
			of this license do not also apply to any third party libraries or components provided with 
			the source distribution. Such third party elements are licensed under their own separate 
			agreements.

	THIS SOFTWARE IS PROVIDED BY DARRAGH COY ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 
	BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
	ARE DISCLAIMED. IN NO EVENT SHALL DARRAGH COY BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
	EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 
	OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 
	OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 
	IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

//##############################################################################
//##############################################################################
//##############################################################################
//##############################################################################
//
//
// Class Name:   GameObject_Missile
// Purpose:      Class describing a missile in the game. The missile seeks towards
//               its target but burns out after a while if it has not hit a target.
//
//
//##############################################################################
//##############################################################################
//##############################################################################
//##############################################################################

#include "GameObject_Missile.h"

//------------------------------------------------------------------------------
// Constant initialisation
//------------------------------------------------------------------------------

const float      GameObject_Missile::LIVE_TIME                      = 8;        // time missiles live for in seconds
const float      GameObject_Missile::HEALTH                         = 50;       // health a missile has
const float      GameObject_Missile::DAMAGE                         = 1500;     // damaged inflicted by a missile hit
const float      GameObject_Missile::SPEED                          = 4500;     // missile top speed
const float      GameObject_Missile::BOUNDING_SPHERE_SCALE          = 8;        // Amount to scale the missile bounding sphere by when testing for collisions.
const float      GameObject_Missile::ACCELERATION                   = 14000;    // missile acceleration
const float      GameObject_Missile::EXPLOSION_TIME                 = 0.5f;     // time the missile explosion lasts for in seconds
const float      GameObject_Missile::EXPLOSION_SCALE                = 32.0f;    // scale of the missile explosion
const float      GameObject_Missile::PARTICLE_INTERVAL              = 0.05f;    // amount of time in seconds between bursts of particles coming from the missile
const float      GameObject_Missile::PARTICLE_TIME1                 = 0.1f;     // missile particle live time 1
const float      GameObject_Missile::PARTICLE_TIME2                 = 0.2f;     // missile particle live time 2
const float      GameObject_Missile::PARTICLE_COLOR1_R              = 1.0f;     // missile particle color: red
const float      GameObject_Missile::PARTICLE_COLOR1_G              = 1.0f;     // missile particle color: green
const float      GameObject_Missile::PARTICLE_COLOR1_B              = 0.0f;     // missile particle color: blue
const float      GameObject_Missile::PARTICLE_COLOR2_R              = 1.0f;     // missile particle color: red
const float      GameObject_Missile::PARTICLE_COLOR2_G              = 0.0f;     // missile particle color: green
const float      GameObject_Missile::PARTICLE_COLOR2_B              = 0.0f;     // missile particle color: blue
const float      GameObject_Missile::PARTICLE_SIZE1                 = 4.0f;     // the size of missile particles
const float      GameObject_Missile::PARTICLE_SIZE2                 = 16.0f;    // the size of missile particles
const float      GameObject_Missile::PARTICLE_DIR_RANDOMNESS        = 0.05f;    // amount of variation from missiles velocity direction particles can go
const float      GameObject_Missile::PARTICLE_VEL_RANDOMNESS        = 0.03f;    // amount of variation from missiles velocity amount particles can have
const float      GameObject_Missile::PARTICLE_CONST_VEL_RANDOMNESS  = 50;       // amount of random variation in particle velocity to apply, regardless of ships speed
const float      GameObject_Missile::PARTICLE_OFFSET                = 1.5f;     // offset from the center of the mesh to bounding sphere end for particle spawns. Given in percent.
const unsigned   GameObject_Missile::PARTICLE_COUNT                 = 24;       // number of particles to create in an missile particle stream
const float      GameObject_Missile::NOISE_ENGINE_DISTANCE          = 32000;    // distance from which the engine noise can be heard
const float      GameObject_Missile::NOISE_FIRE_DISTANCE            = 48000;    // distance from which the firing noise can be heard

/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

Name:     GameObject_Missile::GameObject_Missile()
Purpose:  Missile constructor. Constructs the missile object.
Params:   missile position. missile firer and target.
Returns:  n/a

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/

GameObject_Missile::GameObject_Missile
(
    float           pos_x,      // initial position: x
    float           pos_y,      // initial position: y
    float           pos_z,      // initial position: z
    GameObject *    firer,      // hook to the object that fired the missile. the missile will not be able to hit this object
    GameObject *    target      // hook to the object that the missile is targeting. missile will seek towards this target and attempt to hit it.
)
: MovingGameObject( pos_x , pos_y , pos_z , MeshManager::getMeshMissile() )
{
    // Save the pointers to the firer and target:

    this->firer  = firer;
    this->target = target;

    // Set the maximum velocity of this object to the missile speed:

    this->maxVelocity = SPEED;

    // Set the missiles acceleration value:

    this->acceleration = ACCELERATION;

    // Make the missile as not being solid:

    this->solid = false;

    // Missile is not indestructable:

    this->indestructable = false;
    this->health         = 1;

    // Set the amount of time left for the missile to live

    this->timeLeft = LIVE_TIME;

    // Set the amount of time to the next batch of particles to zero

    this->timeToParticles = 0;

    // Set the name of this object

    this->name = "Missile";
}

/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

Name:     GameObject_Missile::~GameObject_Missile()
Purpose:  Destructor. Spawns an explosion.
Params:   none
Returns:  n/a

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/

GameObject_Missile::~GameObject_Missile()
{
    // Interpret the scene this object belongs to as a game scene:

    GameScene * scene = (GameScene*) this->getScene();

    // Abort if the scene doesn't exist

    if ( scene == 0 ) return;

    // Make an explosion:

    scene->addObject
    (
        new GameObject_Explosion
        (
            this->getPosX(),
            this->getPosY(),
            this->getPosZ(),
            this->getMesh()->getSphereRadius() * EXPLOSION_SCALE,
            EXPLOSION_TIME
        )
    );

    // Remove the engine noise from the scene

    scene->deleteSound(sndEngineID);
}

/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

Name:     GameObject_Missile::onFrame()
Purpose:  On frame event for the missile. makes particles every so often and
          checks for hits against other objects.
Params:   Time the last frame took in seconds
Returns:  none

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/

void GameObject_Missile::onFrame( float frameTime )
{
    //--------------------------------------------------------------------------
    // If this is the first frame then create a firing noise and an engine noise:
    //--------------------------------------------------------------------------

    if ( timeLeft == LIVE_TIME )
    {
        //----------------------------------------------------------------------
        // First on frame event for the missile
        //----------------------------------------------------------------------

        // Interpret the scene this object belongs to as a game scene:

        GameScene * scene = (GameScene*) this->getScene();

        // Create a firing noise:

        scene->addSound
        (
            SoundManager::getSndRocketFire(),   // sound to add
            getPosX(),                          // sound position
            getPosY(),                          // sound position
            getPosZ(),                          // sound position
            NOISE_FIRE_DISTANCE,                // distance over which sound can be heard
            1,                                  // frequency of sound
            false                               // looping sound ?
        );

        // Create a looping sound for the engine noise and save its ID

        this->sndEngineID = scene->addSound
        (
            SoundManager::getSndRocketEngine(), // sound to add
            getPosX(),                          // sound position
            getPosY(),                          // sound position
            getPosZ(),                          // sound position
            NOISE_ENGINE_DISTANCE,              // distance over which sound can be heard
            1,                                  // frequency of sound
            true                                // looping sound ?
        );
    }
    else
    {
        //----------------------------------------------------------------------
        // Not the first time the on frame event was called:
        //----------------------------------------------------------------------

        // Interpret the scene this object belongs to as a game scene:

        GameScene * scene = (GameScene*) this->getScene();

        // Update the position of the engine sound to the misiles current position

        scene->updateSoundPosition
        (
            sndEngineID,        // ID of sound to update
            getPosX(),          // sound position
            getPosY(),          // sound position
            getPosZ()           // sound position
        );
    }

    //--------------------------------------------------------------------------
    // Try and spawn another batch of particles
    //--------------------------------------------------------------------------

    spawnParticles(frameTime);

    //--------------------------------------------------------------------------
    // Missile piloting code
    //--------------------------------------------------------------------------

    // See if target object is still in the scene:

    if ( this->getScene()->objectExists( target ) )
    {
        // face the target object

        this->faceObject(target);
    }

    //--------------------------------------------------------------------------
    // Decrement the missiles time to live:
    //--------------------------------------------------------------------------

    timeLeft -= frameTime;

    // See if the missile should be killed:

    if ( timeLeft <= 0 )
    {
        // Kill this missile:

        this->getScene()->deleteObject(this);

        // Do no further processing with the missile

        return;
    }

    //--------------------------------------------------------------------------
    // Cast a ray in the missiles forward direction and see if it hits anything:
    // but exclude the firing ship from this ray cast:
    //--------------------------------------------------------------------------

    // Make up a vector with missile position:

    float pos[3] =
    {
        this->getPosX(),
        this->getPosY(),
        this->getPosZ()
    };

    // Make up a vector with missile forward direction:

    float dir[3]; getAxisZ(dir);

    // Make a vector to store ray cast hit point:

    float hit[3];

    // Do a ray cast from the missiles position and see what it hits:

    GameObject * hitObject = getScene()->rayCast
    (
        this->firer,    // object to exclude from cast
        pos,            // origin of cast
        dir,            // direction of cast
        hit             // vector to store the ray cast hit point in
    );

    // If there was a hit, get the lenght to the hit point:

    if ( hitObject )
    {
        //----------------------------------------------------------------------
        // Get distance to the hit point:
        //----------------------------------------------------------------------

        float dist = mlDistance3d( pos , hit );

        //----------------------------------------------------------------------
        // If the distance is less than the bounding sphere radius of the missile
        // times the sphere scale then hurt the hit object and kill the missile:
        //----------------------------------------------------------------------

        if ( dist < this->getMesh()->getSphereRadius() * BOUNDING_SPHERE_SCALE )
        {
            // Damage the hit object by the missile damage amount:

            hitObject->damage( DAMAGE , firer );

            // Kill this missile:

            this->getScene()->deleteObject(this);
        }

    }   // end if the ray cast hit an object
}

/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

Name:     GameObject_Missile::spawnParticles()
Purpose:  Creates particles behind the missile every so often.
Params:   Time the last frame took in seconds
Returns:  none

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/

void GameObject_Missile::spawnParticles( float frameTime )
{
    //--------------------------------------------------------------------------
    // Get the GameObjecScene the missile belongs to, and assume that in actual
    // fact it is of the derived 'GameScene' type:
    //--------------------------------------------------------------------------

    GameScene * scene = (GameScene*)getScene();

    //--------------------------------------------------------------------------
    // Now see if it is time to spawn another particle set:
    //--------------------------------------------------------------------------

    if ( timeToParticles <= 0 )
    {
        //----------------------------------------------------------------------
        // Decide on particle speed
        //----------------------------------------------------------------------

        // Get the speed that the ship is currently moving at

        float moveSpeed1 = mlLenght3d(velocity);

        // Get a random fraction of this move speed to vary it by

        float random = moveSpeed1 * mlRandomFloat() * PARTICLE_VEL_RANDOMNESS;

        // Now vary the first move speed:

        moveSpeed1 -= random;

        // Now make a second move speed, varying it randomnly by constant velocity randomness

        float moveSpeed2 = moveSpeed1 - mlRandomFloat() * PARTICLE_CONST_VEL_RANDOMNESS;

        //----------------------------------------------------------------------
        // Decide on particle directions
        //----------------------------------------------------------------------

        // Get two copies of the velocity vector and normalise

        float velocityDir1[3] =
        {
            velocity[0],
            velocity[1],
            velocity[2],
        };

        // Only normalise it if there is a velocity direction, otherwise pick
        // a new random direction and normalise:

        if ( velocityDir1[0] != 0 || velocityDir1[1] != 0 || velocityDir1[2] != 0 )
        {
            // Have a veloctity direction: normalise

            mlNormalise3d(velocityDir1);
        }
        else
        {
            // Don't have a velocity direction: make one up

            velocityDir1[0] = mlRandomFloat() - 0.5f;
            velocityDir1[1] = mlRandomFloat() - 0.5f;
            velocityDir1[2] = mlRandomFloat() - 0.5f;

            // Normalise this direction

            mlNormalise3d(velocityDir1);
        }

        float velocityDir2[3] =
        {
            velocityDir1[0],
            velocityDir1[1],
            velocityDir1[2],
        };

        // Now randomly vary these directions

        velocityDir1[0] += mlRandomFloat() * PARTICLE_DIR_RANDOMNESS * 2 - PARTICLE_DIR_RANDOMNESS;
        velocityDir1[1] += mlRandomFloat() * PARTICLE_DIR_RANDOMNESS * 2 - PARTICLE_DIR_RANDOMNESS;
        velocityDir1[2] += mlRandomFloat() * PARTICLE_DIR_RANDOMNESS * 2 - PARTICLE_DIR_RANDOMNESS;

        velocityDir2[0] += mlRandomFloat() * PARTICLE_DIR_RANDOMNESS * 2 - PARTICLE_DIR_RANDOMNESS;
        velocityDir2[1] += mlRandomFloat() * PARTICLE_DIR_RANDOMNESS * 2 - PARTICLE_DIR_RANDOMNESS;
        velocityDir2[2] += mlRandomFloat() * PARTICLE_DIR_RANDOMNESS * 2 - PARTICLE_DIR_RANDOMNESS;

        //----------------------------------------------------------------------
        // Decide on particles position
        //----------------------------------------------------------------------

        // Get the missiles orientation matrix;

        float orientation[9]; getOrientationMatrix(orientation);

        // Get the mesh bounding sphere size and multiply by particle position offset:

        float offset = getMesh()->getSphereRadius() * PARTICLE_OFFSET;

        // Now make a position going from the center of the missile object in the reverse
        // direction of its forward vector and by the offset amount:

        float particlePosition[3] =
        {
            getPosX() - orientation[6] * offset,
            getPosY() - orientation[7] * offset,
            getPosZ() - orientation[8] * offset,
        };

        //----------------------------------------------------------------------
        // Spawn the missiles particles
        //----------------------------------------------------------------------

        scene->createStream
        (
            particlePosition[0],
            particlePosition[1],
            particlePosition[2],
            PARTICLE_COLOR1_R,
            PARTICLE_COLOR1_G,
            PARTICLE_COLOR1_B,
            PARTICLE_COLOR2_R,
            PARTICLE_COLOR2_G,
            PARTICLE_COLOR2_B,
            PARTICLE_SIZE1,
            PARTICLE_SIZE2,
            moveSpeed1,
            moveSpeed2,
            velocityDir1[0],
            velocityDir1[1],
            velocityDir1[2],
            velocityDir2[0],
            velocityDir2[1],
            velocityDir2[2],
            PARTICLE_TIME1,
            PARTICLE_TIME2,
            PARTICLE_COUNT
        );

        //----------------------------------------------------------------------
        // Set time to next particle as engine particle interval
        //----------------------------------------------------------------------

        timeToParticles = PARTICLE_INTERVAL;
    }
    else
    {
        //----------------------------------------------------------------------
        // Not time to make a particle: decrease the amount of time left
        // to make a particle by the frame time:
        //----------------------------------------------------------------------

        timeToParticles -= frameTime;
    }
}
