/*
	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_Emp
// Purpose:      The EMP bomb is a special kind of object that acts as a missile
//               counter measure. It basically destroys all missiles within a
//               certain range and emits bolts of energy as a special effect.
//
//
//##############################################################################
//##############################################################################
//##############################################################################
//##############################################################################

#include "GameObject_Emp.h"

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

const float GameObject_Emp::LIVE_TIME = 2.5f;       // time the emp bomb will live for
const float GameObject_Emp::RANGE     = 15000.0f;   // range in which the emp bomb will destroy missiles

// Lightning stalk effects

const float GameObject_Emp::STALK_CHANGE_TIME       = 0.02f;    // time it takes for the lightning stalks to change in seconds
const float GameObject_Emp::STALK_WIDTH             = 8.0f;     // width of lightning stalks
const float GameObject_Emp::STALK_PART_MIN_LENGHT   = 50;       // minimum lenght of each stalk part
const float GameObject_Emp::STALK_PART_MAX_LENGHT   = 400;      // maximum lenght of each stalk part
const float GameObject_Emp::STALK_PART_COLOR_R      = 0.5f;     // color of each stalk part: red
const float GameObject_Emp::STALK_PART_COLOR_G      = 0.5f;     // color of each stalk part: green
const float GameObject_Emp::STALK_PART_COLOR_B      = 1.0f;     // color of each stalk part: blue
const float GameObject_Emp::NOISE_FIRE_DISTANCE     = 48000;    // distance from which the emp firing noise can be heard
const float GameObject_Emp::NOISE_AMBIENT_DISTANCE  = 32000;    // distance from which the emp ambient noise can be heard

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

Name:     GameObject_Emp::GameObject_Emp()
Purpose:  Constructor for the emp object.
Params:   Pointer to the object which fired the emp
Returns:  n/a

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

GameObject_Emp::GameObject_Emp( GameObject * firer ) : MovingGameObject(0,0,0,0)
{
    // Save the object which fired the emp

    this->firer = firer;

    // Set the time left for the emp bomb

    this->timeLeft = LIVE_TIME;

    // Set the time since the lightning stalks last changed to zero

    this->timeSinceStalkChange = 0;

    // If the firer exists then set the position of the emp bomb:

    if ( firer )
    {
        emp_position[0] = firer->getPosX();
        emp_position[1] = firer->getPosY();
        emp_position[2] = firer->getPosZ();
    }

    // Randomly decide on lightning stalk points:

    generateLightningStalkPoints();

    // Set the emps ambient sound id to zero initially

    this->sndEmpAmbientID = 0;

    // This is not a targetable object

    targetable = false;
}

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

Name:     GameObject_Emp::~GameObject_Emp()
Purpose:  Destructor for the emp object.
Params:   n/a
Returns:  n/a

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

GameObject_Emp::~GameObject_Emp()
{
    // 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;

    // Remove the emp ambient noise from the scene

    scene->deleteSound(sndEmpAmbientID);
}

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

Name:     GameObject_Emp::onFrame()
Purpose:  On frame event for the emp bomb object. kills missiles in range.
Params:   Time the last frame took in seconds
Returns:  none

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

void GameObject_Emp::onFrame( float frameTime  )
{
    //--------------------------------------------------------------------------
    // Get the objects scene and interpret as a game scene
    //--------------------------------------------------------------------------

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

    //--------------------------------------------------------------------------
    // Update the emp bombs position to that of the firer
    //--------------------------------------------------------------------------

    // See if the firer still exists in the scene:

    if ( scene->objectExists(firer) )
    {
        // Firer still exists. update the emp bombs position:

        emp_position[0] = firer->getPosX();
        emp_position[1] = firer->getPosY();
        emp_position[2] = firer->getPosZ();
    }

    //--------------------------------------------------------------------------
    // See if this is the first frame the emp has been alive for
    //--------------------------------------------------------------------------

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

        // Create a firing noise:

        scene->addSound
        (
            SoundManager::getSndEmpFire(),      // sound to add
            emp_position[0],                    // sound position
            emp_position[1],                    // sound position
            emp_position[2],                    // 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 emo ambient noise and save its ID

        this->sndEmpAmbientID = scene->addSound
        (
            SoundManager::getSndEmpAmbient(),   // sound to add
            emp_position[0],                    // sound position
            emp_position[1],                    // sound position
            emp_position[2],                    // sound position
            NOISE_AMBIENT_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:
        //----------------------------------------------------------------------

        // Update the position of the emp ambient sound to the emps current position

        scene->updateSoundPosition
        (
            sndEmpAmbientID,    // ID of sound to update
            emp_position[0],    // sound position
            emp_position[1],    // sound position
            emp_position[2]     // sound position
        );
    }

    //--------------------------------------------------------------------------
    // Death check
    //--------------------------------------------------------------------------

    // Decrement the emp bombs time to live by the frame time

    timeLeft -= frameTime;

    // If the emp bombs time is up then kill the object and abort abort:

    if ( timeLeft <= 0 )
    {
        // Kill the object: with the scene

        scene->deleteObject(this);

        // Do no further processing on it

        return;
    }

    //--------------------------------------------------------------------------
    // See if it is time to change the lightning stalks again:
    //--------------------------------------------------------------------------

    if (  this->timeSinceStalkChange >= STALK_CHANGE_TIME )
    {
        // Change the lightning stalks again:

        generateLightningStalkPoints();

        // Set the time since the last change to zero

        timeSinceStalkChange = 0;
    }
    else
    {
        // Increment the time since the last lightning stalk change

        timeSinceStalkChange += frameTime;
    }

    //--------------------------------------------------------------------------
    // Check for missiles in the scene that are within range:
    //--------------------------------------------------------------------------

    // Decide on the range of the emp bomb first from its time left to live: range dimishes with time

    float range = ( this->timeLeft / LIVE_TIME ) * RANGE;

    // Now get the first missile in the scene:

    GameObject * missile = scene->findObject("Missile");

    // See if this missile exists:

    if ( missile != 0 )
    {
        // Makeup a vector with the missiles position

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

        // See if the distance to the missile is within range

        if ( mlDistance3d( emp_position , missile_position ) <= range )
        {
            // Missile in range of emp: Kill this missile off:

            missile->damage( 99999999.0f , this );
        }
    }

    // Go through all the other missiles in the scene and kill those in range:
    
    missile = scene->findNextObject(missile);

    while ( missile != 0 )
    {
        // Makeup a vector with the missiles position

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

        // See if the distance to the missile is within range

        if ( mlDistance3d( emp_position , missile_position ) <= range )
        {
            // Missile in range of emp: Kill this missile off:

            missile->damage( 99999999.0f , this );
        }
        
        // Next missle:
        
        missile = scene->findNextObject(missile);
    }
}

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

Name:     GameObject_Emp::onDraw()
Purpose:  On draw event for the emp bomb object. draws lightning stalks.
Params:   Camera the scene is being viewed from
Returns:  none

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

void GameObject_Emp::onDraw( Camera * camera )
{
    //--------------------------------------------------------------------------
    // update the emps position first: otherwise stalks will jitter as players
    // ship moves:
    //--------------------------------------------------------------------------

    // Get the objects scene

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

    // If the object is not part of a scene then abort

    if ( scene == 0 ) return;

    // See if the firer still exists in the scene:

    if ( scene->objectExists(firer) )
    {
        // Firer still exists. update the emp bombs position:

        emp_position[0] = firer->getPosX();
        emp_position[1] = firer->getPosY();
        emp_position[2] = firer->getPosZ();
    }

    //--------------------------------------------------------------------------
    // Update the position of the emp ambient sound to the emps current position
    //--------------------------------------------------------------------------

    scene->updateSoundPosition
    (
        sndEmpAmbientID,    // ID of sound to update
        emp_position[0],    // sound position
        emp_position[1],    // sound position
        emp_position[2]     // sound position
    );

    //--------------------------------------------------------------------------
    // Save and setup opengl state
    //--------------------------------------------------------------------------

    glPushAttrib(GL_ENABLE_BIT);

    glEnable    (GL_TEXTURE_2D);
    glEnable    (GL_BLEND);
    glDisable   (GL_LIGHTING);
    glDisable   (GL_CULL_FACE);

    glDepthMask(GL_FALSE);

    glBlendFunc( GL_SRC_ALPHA , GL_ONE );

    // Bind the same texture used for lasers with the lightning stalk:

    GLTextureManager::getTexBeam()->bind();

    //--------------------------------------------------------------------------
    // Decide on the color of the lightning stalk based on how long the emp
    // has left:
    //--------------------------------------------------------------------------

    float color_r = ( timeLeft / LIVE_TIME ) * STALK_PART_COLOR_R;
    float color_g = ( timeLeft / LIVE_TIME ) * STALK_PART_COLOR_G;
    float color_b = ( timeLeft / LIVE_TIME ) * STALK_PART_COLOR_B;

    //--------------------------------------------------------------------------
    // Draw each lightning stalk:
    //--------------------------------------------------------------------------

    for ( unsigned i = 0 ; i < NUM_STALKS ; i++ )
    {
        // Draw the first part with the start in black: causes lighning stalk to fade in:

        drawLightingStalkPart
        (
            camera,
            stalkPoints[i][ 0 ],
            stalkPoints[i][ 1 ],
            stalkPoints[i][ 2 ],
            stalkPoints[i][ 3 ],
            stalkPoints[i][ 4 ],
            stalkPoints[i][ 5 ],
            0,
            0,
            0,
            color_r,
            color_g,
            color_b
        );

        // Draw each part up past the start part until the last part:

        for ( unsigned j = 1 ; j < NUM_STALK_PARTS - 1 ; j++ )
        {
            // Draw this part:

            drawLightingStalkPart
            (
                camera,
                stalkPoints[i][ j * 3 + 0 ],
                stalkPoints[i][ j * 3 + 1 ],
                stalkPoints[i][ j * 3 + 2 ],
                stalkPoints[i][ j * 3 + 3 ],
                stalkPoints[i][ j * 3 + 4 ],
                stalkPoints[i][ j * 3 + 5 ],
                color_r,
                color_g,
                color_b,
                color_r,
                color_g,
                color_b
            );
        }

        // Draw the last part with the end in black: causes lighning stalk to fade out:

        drawLightingStalkPart
        (
            camera,
            stalkPoints[i][ ( NUM_STALK_PARTS - 1 ) * 3 + 0 ],
            stalkPoints[i][ ( NUM_STALK_PARTS - 1 ) * 3 + 1 ],
            stalkPoints[i][ ( NUM_STALK_PARTS - 1 ) * 3 + 2 ],
            stalkPoints[i][ ( NUM_STALK_PARTS - 1 ) * 3 + 3 ],
            stalkPoints[i][ ( NUM_STALK_PARTS - 1 ) * 3 + 4 ],
            stalkPoints[i][ ( NUM_STALK_PARTS - 1 ) * 3 + 5 ],
            color_r,
            color_g,
            color_b,
            0,
            0,
            0
        );
    }

    //--------------------------------------------------------------------------
    // Restore opengl state
    //--------------------------------------------------------------------------

    glPopAttrib();
    glDepthMask(GL_TRUE);
}

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

Name:     GameObject_Emp::generateLightningStalkPoints()
Purpose:  Generates a new set of lightning stalk points for the emps lightning
          effect...
Params:   none
Returns:  none

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

void GameObject_Emp::generateLightningStalkPoints()
{
    // Generate each lightning stalk:

    for ( unsigned i = 0 ; i < NUM_STALKS ; i++ )
    {
        // Decide which coordinates of this stalk will be negated

        float nx = mlRandomInt() & 1 ? 1.0f : -1.0f;
        float ny = mlRandomInt() & 1 ? 1.0f : -1.0f;
        float nz = mlRandomInt() & 1 ? 1.0f : -1.0f;

        // Pick 3 random floats between 1 and 0:

        float r1 = mlRandomFloat();
        float r2 = mlRandomFloat();
        float r3 = mlRandomFloat();

        // Pick 3 coordinates based on these random numbers and min/max stalk part lenghts

        float x = r1 * STALK_PART_MIN_LENGHT + ( 1.0f - r1 ) * STALK_PART_MAX_LENGHT;
        float y = r2 * STALK_PART_MIN_LENGHT + ( 1.0f - r2 ) * STALK_PART_MAX_LENGHT;
        float z = r3 * STALK_PART_MIN_LENGHT + ( 1.0f - r3 ) * STALK_PART_MAX_LENGHT;

        // Negate the coordinates by the negatition modifiers

        x *= nx;
        y *= ny;
        z *= nz;

        // Set the first stalk point

        stalkPoints[i][0] = x;
        stalkPoints[i][1] = y;
        stalkPoints[i][2] = z;

        // Generate the rest of the stalk points:

        for ( unsigned j = 1 ; j < NUM_STALK_PARTS + 1 ; j++ )
        {
            // Pick 3 random floats between 1 and 0:

            r1 = mlRandomFloat();
            r2 = mlRandomFloat();
            r3 = mlRandomFloat();

            // Pick 3 coordinates based on these random numbers and min/max stalk part lenghts

            x = r1 * STALK_PART_MIN_LENGHT + ( 1.0f - r1 ) * STALK_PART_MAX_LENGHT;
            y = r2 * STALK_PART_MIN_LENGHT + ( 1.0f - r2 ) * STALK_PART_MAX_LENGHT;
            z = r3 * STALK_PART_MIN_LENGHT + ( 1.0f - r3 ) * STALK_PART_MAX_LENGHT;

            // Negate the coordinates by the negatition modifiers

            x *= nx;
            y *= ny;
            z *= nz;

            // Decide on the stalk point: based on previous stalk point and the 3 random coordinates chosen

            stalkPoints[i][ j * 3 + 0 ] = stalkPoints[i][ j * 3 - 3 ] + x;
            stalkPoints[i][ j * 3 + 1 ] = stalkPoints[i][ j * 3 - 2 ] + y;
            stalkPoints[i][ j * 3 + 2 ] = stalkPoints[i][ j * 3 - 1 ] + z;
        }
    }
}

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

Name:     GameObject_Emp::drawLightingStalkPart()
Purpose:  Draws a part of the lightning stalk.
Params:   as commented
Returns:  none

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

void GameObject_Emp::drawLightingStalkPart
(
    Camera * camera,    // camera the game is being viewed from
    float x1,           // point at start of lightning stalk: x
    float y1,           // point at start of lightning stalk: y
    float z1,           // point at start of lightning stalk: z
    float x2,           // point at end of lightning stalk: x
    float y2,           // point at end of lightning stalk: y
    float z2,           // point at end of lightning stalk: z
    float r1,           // point at start of lightning stalk: color r
    float g1,           // point at start of lightning stalk: color g
    float b1,           // point at start of lightning stalk: color b
    float r2,           // point at end of lightning stalk: color r
    float g2,           // point at end of lightning stalk: color g
    float b2            // point at end of lightning stalk: color b
)
{
    //--------------------------------------------------------------------------
    // Stalk part position is described relative to the emp bombs position:
    // tranlate into a world position:
    //--------------------------------------------------------------------------

    x1 += emp_position[0];
    y1 += emp_position[1];
    z1 += emp_position[2];

    x2 += emp_position[0];
    y2 += emp_position[1];
    z2 += emp_position[2];

    //--------------------------------------------------------------------------
    // Get the position of the camera
    //--------------------------------------------------------------------------

    float cameraPos[3]; camera->getPosition(cameraPos);

    //--------------------------------------------------------------------------
    // Get a vector going from the stalk start to the cameras position
    //--------------------------------------------------------------------------

    float vectorToCamera[3] =
    {
        cameraPos[0] - x1,
        cameraPos[1] - y1,
        cameraPos[2] - z1
    };

    //--------------------------------------------------------------------------
    // Get the stalks un normalised direction vector
    //--------------------------------------------------------------------------

    float stalkVector[3] =
    {
        x2 - x1,
        y2 - y1,
        z2 - z1
    };

    //--------------------------------------------------------------------------
    // Declare a vector that will hold the direction to expand the stalk outwards
    //--------------------------------------------------------------------------

    float expandVector[3];

    //--------------------------------------------------------------------------
    // Do a cross product with the vector to the camera. this will give a vector
    // to expand the thickness of the stalk outwards along
    //--------------------------------------------------------------------------

    mlCross3d( vectorToCamera , stalkVector , expandVector);

    //--------------------------------------------------------------------------
    // Check the magnitude of this vector. If it is very small then that means
    // that the stalkVector and vectorToCamera vectors were almost colinear and
    // hence the expand vector will vary wildy since the camera is nearly or is
    // on the line of the stalk. if this is the case then do not draw the stalk:
    //--------------------------------------------------------------------------

    if ( mlMagnitude3d(expandVector) < 0.01f ) return;

    //--------------------------------------------------------------------------
    // Normalise the expand vector
    //--------------------------------------------------------------------------

    mlNormalise3d( expandVector );

    //--------------------------------------------------------------------------
    // Now draw the stalk, expanded outwards along the expansion vector:
    //--------------------------------------------------------------------------

    glBegin( GL_QUADS );

        glColor4f( r1 , g1 , b1 , 1 );

        glTexCoord2f( 0 , 0 );

        glVertex3f
        (
            x1 - expandVector[0] * STALK_WIDTH,
            y1 - expandVector[1] * STALK_WIDTH,
            z1 - expandVector[2] * STALK_WIDTH
        );

        glColor4f( r1 , g1 , b1 , 1 );

        glTexCoord2f( 0 , 1 );

        glVertex3f
        (
            x1 + expandVector[0] * STALK_WIDTH,
            y1 + expandVector[1] * STALK_WIDTH,
            z1 + expandVector[2] * STALK_WIDTH
        );

        glColor4f( r2 , g2 , b2 , 1 );

        glTexCoord2f( 1 , 1 );

        glVertex3f
        (
            x2 + expandVector[0] * STALK_WIDTH,
            y2 + expandVector[1] * STALK_WIDTH,
            z2 + expandVector[2] * STALK_WIDTH
        );

        glColor4f( r2 , g2 , b2 , 1 );

        glTexCoord2f( 1 , 0 );

        glVertex3f
        (
            x2 - expandVector[0] * STALK_WIDTH,
            y2 - expandVector[1] * STALK_WIDTH,
            z2 - expandVector[2] * STALK_WIDTH
        );

    glEnd();
}
