//----------------------------------------------------------------
// rvMonsterConvoyHover.h
//
// Copyright 2002-2004 Raven Software
//----------------------------------------------------------------




#include "../Game_local.h"

#ifndef __GAME_VEHICLEAI_H__
#include "VehicleAI.h"
#endif

#ifndef __GAME_PROJECTILE_H__
#include "../Projectile.h"
#endif


class rvMonsterConvoyHover : public rvVehicleMonster {
public:
	CLASS_PROTOTYPE ( rvMonsterConvoyHover );

							rvMonsterConvoyHover		( void );
							~rvMonsterConvoyHover		( void );
	void					Save						( idSaveGame *savefile ) const;
	void					Restore						( idRestoreGame *savefile );
	void					InitSpawnArgsVariables		( void );
	void					Spawn						( void );
	void					Think						( void );

private:

	void					Think_Random				( void );
	void					Think_Pathing				( void );

	void					AttackBlaster				( void );
	void					AttackBeam					( void );
	//void					AttackBomb					( void );

	float					angleYaw;			// actually acos( angleYaw ) from player
	float					minYaw;
	float					maxYaw;
	float					desiredHeight;		// actually acos( desiredHeight ) from player
	float					minHeight;
	float					maxHeight;
	float					distance;			// distance to enemy
	float					minDistance;
	float					maxDistance;
	jointHandle_t			jointGunRight;
	jointHandle_t			jointGunLeft;
	
	int						lastAttackTime;
	int						attackStartTime;

	int						blasterAttackDuration;
	int						blasterAttackRate;
	int						bombAttackDuration;
	int						bombAttackRate;
	
	int						shotCount;

	idPhysics_RigidBody		physicsObj;

	static const int DAMPEN_ANGLE_SAMPLES = 8;
	idAngles				hoverDampening[ DAMPEN_ANGLE_SAMPLES ];

	void					CalcDampening				( const idAngles & cur, idAngles & out );

	stateResult_t			State_Idle					( const stateParms_t& parms );
	stateResult_t			State_BlasterAttack			( const stateParms_t& parms );
	stateResult_t			State_BeamAttack			( const stateParms_t& parms );
	//stateResult_t			State_BombAttack			( const stateParms_t& parms );

	struct {
		bool				pathing:1;
		bool				faceEnemy:1;
		bool				dead:1;
	} myfl;

	virtual void			OnDeath						( void );

	CLASS_STATES_PROTOTYPE ( rvMonsterConvoyHover );
};

CLASS_DECLARATION ( rvVehicleMonster, rvMonsterConvoyHover )
END_CLASS

/*
================
rvMonsterConvoyHover::rvMonsterConvoyHover
================
*/
rvMonsterConvoyHover::rvMonsterConvoyHover ( void ) {
}

/*
================
rvMonsterConvoyHover::~rvMonsterConvoyHover
================
*/
rvMonsterConvoyHover::~rvMonsterConvoyHover ( void ) {
	SetPhysics( NULL );
}

/*
================
rvMonsterConvoyHover::Save
================
*/
void rvMonsterConvoyHover::Save ( idSaveGame *savefile ) const {
	savefile->WriteFloat ( angleYaw );
	savefile->WriteFloat ( desiredHeight );
	savefile->WriteFloat ( distance );

	savefile->WriteInt ( lastAttackTime );
	savefile->WriteInt ( attackStartTime );

	savefile->WriteInt ( shotCount );
	
	savefile->WriteStaticObject ( physicsObj );

	savefile->Write ( hoverDampening, sizeof ( idAngles ) * DAMPEN_ANGLE_SAMPLES );

	savefile->Write( &myfl, sizeof(myfl) );	// cnicholson: Added unsaved var
}

/*
================
rvMonsterConvoyHover::Restore
================
*/
void rvMonsterConvoyHover::Restore ( idRestoreGame *savefile ) {
	savefile->ReadFloat ( angleYaw );
	savefile->ReadFloat ( desiredHeight );
	savefile->ReadFloat ( distance );

	savefile->ReadInt ( lastAttackTime );
	savefile->ReadInt ( attackStartTime );

	savefile->ReadInt ( shotCount );

	physicsObj.SetSelf( this );
	physicsObj.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f );
	savefile->ReadStaticObject ( physicsObj );
	RestorePhysics ( &physicsObj );
	physicsObj.EnableClip();

	savefile->Read ( hoverDampening, sizeof ( idAngles ) * DAMPEN_ANGLE_SAMPLES );

	savefile->Read( &myfl, sizeof(myfl) );	// cnicholson: Added unsaved var

	InitSpawnArgsVariables();
}

void rvMonsterConvoyHover::InitSpawnArgsVariables ( void )
{
	jointGunRight	= animator.GetJointHandle ( spawnArgs.GetString ( "joint_gun_right" ) );
	jointGunLeft	= animator.GetJointHandle ( spawnArgs.GetString ( "joint_gun_left" ) );

	minYaw		= spawnArgs.GetFloat( "minYaw", "0" );
	maxYaw		= spawnArgs.GetFloat( "maxYaw", "360" );
	minHeight	= spawnArgs.GetFloat( "minHeight", "10" );
	maxHeight	= spawnArgs.GetFloat( "maxHeight", "70" );
	minDistance	= spawnArgs.GetFloat( "minDistance", "100" );
	maxDistance	= spawnArgs.GetFloat( "maxDistance", "500" );

	minDistance	*= minDistance;
	maxDistance	*= maxDistance;

	if ( minYaw == 0.0f && maxYaw == 0.0f ) {
		maxYaw = 360.0f;
	}

	blasterAttackDuration	= SEC2MS ( spawnArgs.GetFloat ( "blasterAttackDuration", "1" ) );
	blasterAttackRate		= SEC2MS ( spawnArgs.GetFloat ( "blasterAttackRate", ".25" ) );
	bombAttackDuration		= SEC2MS ( spawnArgs.GetFloat ( "bombAttackDuration", "1" ) );
	bombAttackRate			= SEC2MS ( spawnArgs.GetFloat ( "bombAttackRate", ".25" ) );
}
/*
================
rvMonsterConvoyHover::Spawn
================
*/
void rvMonsterConvoyHover::Spawn ( void ) {
	physicsObj.SetSelf( this );	

	SetClipModel ( physicsObj );

	physicsObj.SetOrigin( GetPhysics()->GetOrigin ( ) );
	physicsObj.SetAxis ( GetPhysics()->GetAxis ( ) );
	physicsObj.SetContents( CONTENTS_BODY );
	physicsObj.SetClipMask( MASK_PLAYERSOLID|CONTENTS_VEHICLECLIP|CONTENTS_FLYCLIP );
	physicsObj.SetFriction ( spawnArgs.GetFloat ( "friction_linear", "1" ), spawnArgs.GetFloat ( "friction_angular", "1" ), spawnArgs.GetFloat ( "friction_contact", "1" ) );
	physicsObj.SetBouncyness ( spawnArgs.GetFloat ( "bouncyness", "0.6" ) );
	physicsObj.SetGravity( vec3_origin );
	SetPhysics( &physicsObj );

	animator.CycleAnim ( ANIMCHANNEL_ALL, animator.GetAnim( spawnArgs.GetString( "anim", "idle" ) ), gameLocal.time, 0 );	

	BecomeActive( TH_THINK );

	InitSpawnArgsVariables();

	shotCount	= 0;

	angleYaw		= rvRandom::flrand( minYaw, maxYaw );
	desiredHeight	= 0.5f * (  maxHeight - minHeight ) + minHeight;
	distance		= 0.5f * (  maxDistance - minDistance ) + minDistance;

	lastAttackTime			= 0;

	SetState( "Idle" );

	myfl.pathing = false;
	myfl.faceEnemy = true;
	myfl.dead = false;

	for ( int i = 0; i < DAMPEN_ANGLE_SAMPLES; i++ ) {
		hoverDampening[ i ].Zero();
	}

	jointHandle_t joint;
	joint = GetAnimator()->GetJointHandle( spawnArgs.GetString ( "joint_thruster", "tail_thrusters" ) );
	if ( joint != INVALID_JOINT ) {
		PlayEffect ( "fx_exhaust", joint, true );
	}
	StartSound ( "snd_flyloop", SND_CHANNEL_ANY, 0, false, NULL );
}

/*
================
rvMonsterConvoyHover::Think
================
*/
void rvMonsterConvoyHover::Think ( void ) {
	if ( !driver ) {
		rvVehicleMonster::Think();
		return;
	}

	if ( !driver->IsDriving() ) {
		GetPhysics()->SetGravity( gameLocal.GetGravity() );
	}

	trace_t trace;
	idVec3 dir = GetPhysics()->GetLinearVelocity();
	dir.Normalize();
	if ( gameLocal.TracePoint( this, trace, GetOrigin(), GetPhysics()->GetOrigin() + dir * 100.0f, MASK_SOLID|CONTENTS_FLYCLIP, 0 ) ) {
		if ( trace.c.contents == CONTENTS_FLYCLIP ) {
			float maxHeight = this->maxHeight * 2.0f;
			GetPhysics()->ApplyImpulse( 0, GetOrigin(), GetPhysics()->GetLinearVelocity() * 2.0f );
			angleYaw		= rvRandom::flrand( minYaw, maxYaw );
			desiredHeight	= idMath::ClampFloat( minHeight, maxHeight, desiredHeight + 5.0f );
			distance		= idMath::ClampFloat( minDistance, maxDistance, distance + 5.0f );
		}
	}

	if ( !driver->CanSee( driver->enemy.ent, false ) ) {
		desiredHeight	= idMath::ClampFloat( minHeight, maxHeight, desiredHeight + 5.0f );
		distance		= idMath::ClampFloat( minDistance, maxDistance, distance - 5.0f );
	}

	if ( myfl.pathing ) {
		Think_Pathing( );
	} else {
		Think_Random( );
	}

	rvVehicleMonster::Think();

	idVec3 vel = GetPhysics()->GetLinearVelocity();
//	vel += idVec3(	idMath::Sin( gameLocal.time + vel.x ), 
//					idMath::Sin( gameLocal.time + vel.y ), 
//					idMath::Sin( gameLocal.time + vel.z ) );
					

	idAngles hover = idAngles(	idMath::ClampFloat( -45.0f, 45.0f, vel.x / 20.0f ), 
								0.0f,
								idMath::ClampFloat( -25.0f, 25.0f, vel.z / 20.0f ) );

	CalcDampening( hover, hover );

	if ( myfl.faceEnemy ) {
		LookAtEntity( gameLocal.GetLocalPlayer(), 0 );
		idAngles angles = GetPhysics()->GetAxis( ).ToAngles( );
		angles.pitch *= 0.35f;
		GetPhysics()->SetAxis( angles.ToMat3( ) * hover.ToMat3() );
	} else {
		GetPhysics()->SetAxis( GetPhysics()->GetAxis() * hover.ToMat3() );
	}
}

/*
================
rvMonsterConvoyHover::Think_Random
================
*/
void rvMonsterConvoyHover::Think_Random ( void ) {
	const idVec3 xAxis( 1.0f, 0.0f, 0.0f );
	const idVec3 yAxis( 0.0f, 1.0f, 0.0f );
	const idVec3 zAxis( 0.0f, 0.0f, 1.0f );
	const float  xSpeed	= 16000.0f;
	const float  ySpeed	= 9000.0f;
	const float  zSpeed	= 9000.0f;

	idEntity * ent		= driver->enemy.ent;
	if ( !ent ) {
		ent = gameLocal.GetLocalPlayer();
	}

	idVec3 toEnemyYaw	= ent->GetPhysics()->GetOrigin() - GetOrigin();
	toEnemyYaw.z		= 0.0f;
	toEnemyYaw.Normalize();
	float yaw			= toEnemyYaw.ToAngles().yaw;
	float height		= GetOrigin().z - ent->GetPhysics()->GetOrigin().z;
	float dist			= GetOrigin().Dist2XY( ent->GetPhysics()->GetOrigin() );

	// yaw
	if ( idMath::Fabs( yaw - angleYaw ) < 10.0f ) {
		angleYaw = rvRandom::flrand( minYaw, maxYaw );
	} else {
		idVec3 impulse = yAxis * ( ( angleYaw < yaw ) ? ySpeed : -ySpeed );
		impulse *= GetPhysics()->GetAxis();
		GetPhysics()->ApplyImpulse( 0, GetOrigin(), impulse );

		// 5% chance of choosing a new angle
		if ( rvRandom::flrand() < 0.05f ) {
			angleYaw = rvRandom::flrand( minYaw, maxYaw );
		}
	}

	// pitch (changed to height for convenience)
	if ( idMath::Fabs( height - desiredHeight ) < 5.0f ) {
		desiredHeight = rvRandom::flrand( minHeight, maxHeight );
	} else if ( GetOrigin().z < ent->GetPhysics()->GetOrigin().z + desiredHeight ) {
		GetPhysics()->ApplyImpulse( 0, GetOrigin(), zAxis * zSpeed );
	} else {
		GetPhysics()->ApplyImpulse( 0, GetOrigin(), zAxis * -zSpeed );
	}

	// distance
	if ( idMath::Fabs( dist - distance ) > 20.0f ) {
		idVec3 impulse	= xAxis * ( ( dist < distance ) ? -xSpeed : xSpeed );
		idMat3 axis		= GetPhysics()->GetAxis();
		axis[ 1 ]		= idVec3( 0.0f, 0.0f, 1.0f );
		axis[ 2 ]		= axis[ 1 ].Cross( axis[ 0 ] );
		impulse			*= axis;
		GetPhysics()->ApplyImpulse( 0, GetOrigin(), impulse );
	} else {
		// just choose a random distance for now
		distance = rvRandom::flrand( minDistance, maxDistance );
	}
}

/*
================
rvMonsterConvoyHover::Think_Pathing
================
*/
void rvMonsterConvoyHover::Think_Pathing ( void ) {
}

/*
================
rvMonsterConvoyHover::AttackBlaster
================
*/
void rvMonsterConvoyHover::AttackBlaster ( void ) {
	idVec3 offset;
	idMat3 axis;
	jointHandle_t joint;	
	
	joint = ((shotCount++)&1) ? jointGunRight : jointGunLeft;
	
	if ( joint == INVALID_JOINT ) {
		return;
	}
	
	if ( !GetJointWorldTransform( joint, gameLocal.time, offset, axis ) ) {
		return;
	}

	PlayEffect ( "fx_muzzleflash", joint );
	idProjectile* proj = gameLocal.SpawnSafeEntityDef<idProjectile>( spawnArgs.GetString("def_attack_blaster") );
	if( proj ) {
		idVec3 dir = GetVectorToEnemy();
		if ( dir.Normalize() == 0.0f ) {
			dir = axis[ 0 ];
		}
		proj->Create( this, offset, dir, NULL );
		proj->Launch( offset, dir, GetPhysics()->GetPushedLinearVelocity() );
	}
}

/*
================
rvMonsterConvoyHover::AttackBeam
================
*/
void rvMonsterConvoyHover::AttackBeam ( void ) {
	idVec3 offset;
	idMat3 axis;
	jointHandle_t joint;	
	
	joint = ((shotCount++)&1) ? jointGunRight : jointGunLeft;
	
	if ( joint == INVALID_JOINT ) {
		return;
	}
	
	if ( !GetJointWorldTransform( joint, gameLocal.time, offset, axis ) ) {
		return;
	}

	PlayEffect ( "fx_muzzleflash", joint );
	idProjectile* proj = gameLocal.SpawnSafeEntityDef<idProjectile>( spawnArgs.GetString("def_attack_blaster") );
	if( proj ) {
		idVec3 dir = GetVectorToEnemy();
		if ( dir.Normalize() == 0.0f ) {
			dir = axis[ 0 ];
		}
		proj->Create( this, offset, dir, NULL );
		proj->Launch( offset, dir, GetPhysics()->GetPushedLinearVelocity() );
	}
}

/*
================
rvMonsterConvoyHover::AttackBomb
================
*
void rvMonsterConvoyHover::AttackBomb ( void ) {
	jointHandle_t joint;	
	joint = ((shotCount++)&1) ? jointGunRight : jointGunLeft;

	if ( joint == INVALID_JOINT ) {
		return;
	}
	
	if ( !GetJointWorldTransform( joint, gameLocal.time, offset, axis ) ) {
		return;
	}

	StartSound ( "snd_bombrun", SND_CHANNEL_ANY, 0, false, NULL );

	PlayEffect ( "fx_bombflash", joint );
	idProjectile* proj = gameLocal.SpawnSafeEntityDef<idProjectile>( spawnArgs.GetString("def_attack_bomb") );
	if( proj ) {
		proj->Create( this, offset, axis[0], NULL );
		proj->Launch( offset, axis[0], GetPhysics()->GetPushedLinearVelocity() );
	}
}
*/



/*
================
rvMonsterConvoyHover::OnDeath
================
*/
void rvMonsterConvoyHover::OnDeath ( void ) {
	myfl.dead = true;
	idVec3 angular = idVec3( rvRandom::flrand( 180.0f, 250.0f ), rvRandom::flrand( 180.0f, 250.0f ), rvRandom::flrand( 180.0f, 250.0f ) );

	physicsObj.SetGravity( gameLocal.GetGravity() );
	physicsObj.SetFriction( 0.0f, 0.0f, 0.0f );
	GetPhysics()->SetAngularVelocity ( angular );

	gameLocal.PlayEffect ( spawnArgs, "fx_death", GetPhysics()->GetOrigin(), (-GetPhysics()->GetGravityNormal()).ToMat3() );
}

/*
================
rvMonsterConvoyHover::CalcDampening
================
*/
void rvMonsterConvoyHover::CalcDampening ( const idAngles & cur, idAngles & out ) {
	idAngles current = cur;	// just incase cur == out
	out = cur;

	for ( int i = 1; i < DAMPEN_ANGLE_SAMPLES; i ++ ) {
		hoverDampening[ i - 1 ] = hoverDampening[ i ];
		out += hoverDampening[ i ];
	}
	hoverDampening[ DAMPEN_ANGLE_SAMPLES - 1 ] = current;

	out *= ( 1.0f / DAMPEN_ANGLE_SAMPLES );
}


/*
===============================================================================

	States 

===============================================================================
*/

CLASS_STATES_DECLARATION ( rvMonsterConvoyHover )
	STATE( "Idle",				rvMonsterConvoyHover::State_Idle )
	STATE( "BlasterAttack",		rvMonsterConvoyHover::State_BlasterAttack )
	STATE( "BeamAttack",		rvMonsterConvoyHover::State_BeamAttack )
//	STATE( "BombAttack",		rvMonsterConvoyHover::State_BombAttack )
END_CLASS_STATES


/*
================
rvMonsterConvoyHover::State_Idle
================
*/
stateResult_t rvMonsterConvoyHover::State_Idle ( const stateParms_t& parms ) {
	if ( driver ) {
		if ( gameLocal.time - lastAttackTime > rvRandom::irand( 1000, 1200 ) && CanSee( driver->enemy.ent, false ) ) {
//			if ( rvRandom::irand( 0, 100 ) < 10 ) {
//				distance = 0;
//				PostState( "BeamAttack" );
//			} else {
				PostState( "BlasterAttack" );
//			}
			PostState( "Idle" );
			return SRESULT_DONE;
		}
		return SRESULT_WAIT;
	}
	return SRESULT_DONE;
}

/*
================
rvMonsterConvoyHover::State_BlasterAttack
================
*/
stateResult_t rvMonsterConvoyHover::State_BlasterAttack ( const stateParms_t& parms ) {
	enum {
		STAGE_INIT,
		STAGE_BLASTER,
		STAGE_BLASTERWAIT
	};
	switch ( parms.stage ) {
		case STAGE_INIT:
			attackStartTime = gameLocal.time;
			return SRESULT_STAGE ( STAGE_BLASTER );
		
		case STAGE_BLASTER:
			lastAttackTime = gameLocal.time;
			AttackBlaster ( );
			return SRESULT_STAGE ( STAGE_BLASTERWAIT );
		
		case STAGE_BLASTERWAIT:
			if ( gameLocal.time - attackStartTime > blasterAttackDuration ) {
				return SRESULT_DONE;
			}
			if ( gameLocal.time - lastAttackTime > blasterAttackRate ) {
				return SRESULT_STAGE ( STAGE_BLASTER );
			}
			return SRESULT_WAIT;
	}
	return SRESULT_ERROR;
}

/*
================
rvMonsterConvoyHover::State_BeamAttack
================
*/
stateResult_t rvMonsterConvoyHover::State_BeamAttack ( const stateParms_t& parms ) {
	enum {
		STAGE_INIT,
		STAGE_BEAM,
		STAGE_BEAMWAIT
	};
	switch ( parms.stage ) {
		case STAGE_INIT:
			attackStartTime = gameLocal.time;
			return SRESULT_STAGE ( STAGE_BEAM );
		
		case STAGE_BEAM:
			lastAttackTime = gameLocal.time;
			AttackBeam( );
			return SRESULT_STAGE ( STAGE_BEAMWAIT );
		
		case STAGE_BEAMWAIT:
			if ( gameLocal.time - attackStartTime > blasterAttackDuration ) {
				return SRESULT_DONE;
			}
			if ( gameLocal.time - lastAttackTime > blasterAttackRate ) {
				return SRESULT_STAGE ( STAGE_BEAM );
			}
			return SRESULT_WAIT;
	}
	return SRESULT_ERROR;
}

/*
================
rvMonsterConvoyHover::State_BombAttack
================
*
stateResult_t rvMonsterConvoyHover::State_BombAttack ( const stateParms_t& parms ) {
	enum {
		STAGE_INIT,
		STAGE_BOMB,
		STAGE_BOMBWAIT
	};
	switch ( parms.stage ) {
		case STAGE_INIT:
			attackStartTime = gameLocal.time;
			return SRESULT_STAGE ( STAGE_BOMB );
		
		case STAGE_BOMB:
			lastAttackTime = gameLocal.time;
			AttackBomb ( );
			return SRESULT_STAGE ( STAGE_BOMBWAIT );
		
		case STAGE_BOMBWAIT:
			if ( !enemy.fl.inFov || gameLocal.time - attackStartTime > bombAttackDuration ) {
				return SRESULT_DONE;
			}
			if ( gameLocal.time - lastAttackTime > bombAttackRate ) {
				return SRESULT_STAGE ( STAGE_BOMB );
			}
			return SRESULT_WAIT;
	}
	return SRESULT_ERROR;
}
*/
