/*
	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:   PlayerAI
// Purpose:      Class for an AI player. Controls a ship and tries to kill player.
//
//
//##############################################################################
//##############################################################################
//##############################################################################
//##############################################################################

#include "PlayerAI.h"

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

const float PlayerAI::MIN_PLAYER_SEPARATION_DISTANCE = 200;     // minimum distance the AI would like to be from the player
const float PlayerAI::MAX_TURN_AMOUNT                = PI_16;   // maximum amount in radians the ai can turn per second
const float PlayerAI::LASER_FIRE_CLOSNESS            = 0.95f;   // controls how close the ais ship must be oriented to players ship to fire
const float PlayerAI::REACTION_SPEED                 = 0.0007f; // determines how fast the AI responds to changes in the players position
const float PlayerAI::MISSILE_FIRE_CHANCE            = 0.02f;   // Likelyness that the AI player will try and fire a missile per second

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

Name:     PlayerAI::PlayerAI()
Purpose:  Constructor for the AI player
Params:   Hook to ship to control , hook to ship to target, AI players credits
Returns:  none

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

PlayerAI::PlayerAI
(
    GameObjectHook * hookShip    ,  // hook to ship ai player will control
    GameObjectHook * hookTarget  ,  // hook to ship ai player will target
    double credits                  // number of credits the ai player will have
)
: Player ( hookShip , credits )
{
    // Save the target ship

    this->hookTarget = hookTarget;

    // Set the player as not being too close to the AI for the last move:

    playerTooCloseLastMove = false;

    // Set the ships max velocity to the maximum speed of the ship

    if ( hookShip->getObject() )
    {
        // Get the AI players ship

        GameObject_Ship * ship = (GameObject_Ship*)hookShip->getObject();

        // Accelerate the ship towards its max speed and keep it at that

        ship->accelerate( ship->getMaxSpeed() );

        // See if the target ship exists:

        if ( hookTarget->getObject() )
        {
            // Get the target ship:

            GameObject_Ship * target = (GameObject_Ship*)hookTarget->getObject();

            // Set the AI's current target position to the players ship position

            targetX = target->getPosX();
            targetY = target->getPosY();
            targetZ = target->getPosZ();
        }

        // If the target exists then make the AI ship face away from it:

        if ( hookTarget->getObject() )
        {
            // Face the player first

            ship->faceObject( hookTarget->getObject() );

            // Now face away from the player:

            ship->rotateY( PI );
        }
    }
}

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

Name:     PlayerAI::makeMove()
Purpose:  Does the move for the AI player
Params:   time last frame took in seconds
Returns:  none

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

void PlayerAI::makeMove( float frameTime )
{
    // If the target or the AIs ship does not exist then abort:

    if ( hookTarget->getObject() == 0 || hookShip->getObject() == 0 )
    {
        // If the AI's ship still exists then cool its lasers:

        if ( hookShip->getObject() )
        {
            ((GameObject_Ship*)hookShip->getObject())->coolLasers(frameTime);
        }

        // Return

        return;
    }

    // Get the AI players ship

    GameObject_Ship * ship = (GameObject_Ship*)hookShip->getObject();

    // Pick a target point for the ship

    pickTargetPoint(frameTime);

    // Get it to face towards the target point, but only rotate gradually:

    float point[3] = { targetX, targetY , targetZ };

    ship->facePoint( point, MAX_TURN_AMOUNT * frameTime );

    // Decide whether to fire the lasers at the player or not

    decideToFireLasers(frameTime);

    // Decide whether to fire the AI ships missiles or not

    decideToFireMissiles(frameTime);
}

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

Name:     PlayerAI::pickTargetPoint()
Purpose:  Decides what point the AI player will try and move towards.
Params:   Time the last frame took in seconds
Returns:  none

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

void PlayerAI::pickTargetPoint( float frameTime )
{
    //--------------------------------------------------------------------------
    // If the target ship or ais ship is null then abort
    //--------------------------------------------------------------------------

    if ( hookTarget->getObject() == 0 || hookShip->getObject() == 0 ) return;

    //--------------------------------------------------------------------------
    // Get the player and ai ships
    //--------------------------------------------------------------------------

    GameObject_Ship * shipAI      = (GameObject_Ship*)hookShip->getObject();
    GameObject_Ship * shipPlayer  = (GameObject_Ship*)hookTarget->getObject();

    //--------------------------------------------------------------------------
    // Get distance between AI ship and player ship
    //--------------------------------------------------------------------------

    float playerPos[] =
    {
        shipPlayer->getPosX(),
        shipPlayer->getPosY(),
        shipPlayer->getPosZ()
    };

    float aiPos[] =
    {
        shipAI->getPosX(),
        shipAI->getPosY(),
        shipAI->getPosZ()
    };

    float dist = mlDistance3d( playerPos , aiPos );

    //--------------------------------------------------------------------------
    // See if the players ship is too close to the ai players ship:
    //--------------------------------------------------------------------------

    bool tooClose;

    // If the player was too close the last time, require twice the normal clearance

    if (  playerTooCloseLastMove )
    {
        tooClose = dist < MIN_PLAYER_SEPARATION_DISTANCE * 1 ? true : false;
    }
    else
    {
        tooClose = dist < MIN_PLAYER_SEPARATION_DISTANCE ? true : false;
    }

    //--------------------------------------------------------------------------
    // Decide on target point differently if player is too close to ai:
    //--------------------------------------------------------------------------

    if ( tooClose )
    {
        // If player wasn't too close last time set a new target point:

        if ( playerTooCloseLastMove == false )
        {
            //------------------------------------------------------------------
            // Hold a direction vector that will give the direction out from the
            // target ships postion the ai player wants to move to:
            //------------------------------------------------------------------

            float moveDirection[3];

            // Randomnly decide on a direction from the players ship center to move to:

            switch ( mlRandomInt() & 3 )
            {
                case 0:
                {
                    // Use the up vector of the ship as the direction:

                    shipAI->getAxisY(moveDirection);

                } break;

                case 1:
                {
                    // Use the left vector of the ship as the direction:

                    shipAI->getAxisX(moveDirection);

                } break;
            }

            // Randomnly reverse that direction:

            if ( rand() & 1 )
            {
                moveDirection[0] *= -1;
                moveDirection[1] *= -1;
                moveDirection[2] *= -1;
            }

            //------------------------------------------------------------------
            // Select a new target point from the center of the players ship,
            // offseted by the direction vector times twice the min separation
            // distance. this will cause the ai ship to turn away from the
            // players ship in some way or another..
            //------------------------------------------------------------------

            targetX = playerPos[0] + moveDirection[0] * MIN_PLAYER_SEPARATION_DISTANCE * 2;
            targetY = playerPos[1] + moveDirection[1] * MIN_PLAYER_SEPARATION_DISTANCE * 2;
            targetZ = playerPos[2] + moveDirection[2] * MIN_PLAYER_SEPARATION_DISTANCE * 2;
        }

        // Set the flag for the player being too close last move to true

        playerTooCloseLastMove = true;
    }
    else
    {
        // Figure out how much to update the new target position by the frame time

        float t = powf( REACTION_SPEED , frameTime );

        // AI ship not to close to player: target point is player ship position

        targetX = t * targetX + shipPlayer->getPosX() * ( 1.0f - t );
        targetY = t * targetY + shipPlayer->getPosY() * ( 1.0f - t );
        targetZ = t * targetZ + shipPlayer->getPosZ() * ( 1.0f - t );

        // Set the flag for the player being too close last move to false

        playerTooCloseLastMove = false;
    }
}

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

Name:     PlayerAI::decideToFireLasers()
Purpose:  Decides whether the AI player will fire its lasers at the players
          ship or not..
Params:   Time the last frame took in seconds
Returns:  none

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

void PlayerAI::decideToFireLasers( float frameTime )
{
    //--------------------------------------------------------------------------
    // If the target ship or ais ship is null then abort
    //--------------------------------------------------------------------------

    if ( hookTarget->getObject() == 0 || hookShip->getObject() == 0 ) return;

    //--------------------------------------------------------------------------
    // Get the player and ai ships
    //--------------------------------------------------------------------------

    GameObject_Ship * shipAI      = (GameObject_Ship*)hookShip->getObject();
    GameObject_Ship * shipPlayer  = (GameObject_Ship*)hookTarget->getObject();

    //--------------------------------------------------------------------------
    // Get the players position and transform into object coordinates
    //--------------------------------------------------------------------------

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

    // Transform into the object coordinates of the AIs ship

    shipAI->toObjectCoordinates(playerPosition);

    // Normalise the players position

    mlNormalise3d(playerPosition);

    //--------------------------------------------------------------------------
    // Now the the z component of the players position gives the angle between
    // the ais forward vector and the players position: use this to determine
    // whether to fire or not:
    //--------------------------------------------------------------------------

    if ( playerPosition[2] > LASER_FIRE_CLOSNESS )
    {
        // Can fire: fire the AI players lasers

        shipAI->fireLasers(frameTime);
    }
    else
    {
        // Can't fire: cool the AI players lasers

        shipAI->coolLasers(frameTime);
    }
}

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

Name:     PlayerAI::decideToFireMissiles()
Purpose:  Decide whether to fire the AI players missiles or not
Params:   Time the last frame took in seconds
Returns:  none

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

void PlayerAI::decideToFireMissiles( float frameTime )
{
    // Get the player and AI ship:

    GameObject_Ship * shipAI      = (GameObject_Ship*)hookShip->getObject();
    GameObject_Ship * shipPlayer  = (GameObject_Ship*)hookTarget->getObject();

    // If either is null then abort:

    if ( shipAI == 0 || shipPlayer == 0 ) return;

    // If AI player has no missiles then abort:

    if ( shipAI->getNumMissiles() == 0 ) return;

    // Get the chance of a missile being fired based on chance per second and frame time

    float chance = frameTime * MISSILE_FIRE_CHANCE;

    // Now see if we can fire a missile:

    if ( mlRandomFloat() < chance )
    {
        // Fire a missile at the player

        shipAI->fireMissile(shipPlayer);
    }
}
