/*****************************************************************************
 * $Id: game_base_combat.cc,v 1.61 2006/08/10 03:18:58 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	Air combat.
 *//*
 * LEGAL:	COPYRIGHT (C) 2004 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE (GPL).
 *****************************************************************************/

#define GAME_BASE_COMBAT_CC 1
#include "game_base_common.hh"
#include "game_base_combat.hh"
#include "game_base_craft.hh"
#include "eng_dyna.hh"
#include "eng_tex.hh"
#include "gfx_random.hh"

// Single gun for jet fighters.
static const LocalVertex sGunPort1Eye[] = 
{
	LocalVertex( WORLD(-1.0),		WORLD(-3.0),	WORLD(-1.0) )
};
static const LocalVertex sGunPort1Chase[] = 
{
	LocalVertex( WORLD(-1.0),		WORLD(-1.0),	WORLD(-70.0) )
};

// P-51 Mustang has 6 Browning .50 caliber machine guns at middle of wings.
// Gun fire is animated as the phosporous tracer rounds.
static const LocalVertex sGunPort6Eye[] = 
{
	LocalVertex( WORLD(-10.0      ),	WORLD(-3.0),	WORLD(-8.0) ),
	LocalVertex( WORLD(-12.0 - 4.0),	WORLD(-3.0),	WORLD(-8.0) ),
	LocalVertex( WORLD(-14.0 - 8.0),	WORLD(-3.0),	WORLD(-8.0) ),
	LocalVertex( WORLD( 10.0      ),	WORLD(-3.0),	WORLD(-8.0) ),
	LocalVertex( WORLD( 12.0 + 4.0),	WORLD(-3.0),	WORLD(-8.0) ),
	LocalVertex( WORLD( 14.0 + 8.0),	WORLD(-3.0),	WORLD(-8.0) )
};
static const LocalVertex sGunPort6Chase[] = 
{
	LocalVertex( WORLD(-10.0      ),	WORLD(-1.0),	WORLD(-8.0) ),
	LocalVertex( WORLD(-12.0 - 1.0),	WORLD(-1.0),	WORLD(-8.0) ),
	LocalVertex( WORLD(-14.0 - 2.0),	WORLD(-1.0),	WORLD(-8.0) ),
	LocalVertex( WORLD( 10.0      ),	WORLD(-1.0),	WORLD(-8.0) ),
	LocalVertex( WORLD( 12.0 + 1.0),	WORLD(-1.0),	WORLD(-8.0) ),
	LocalVertex( WORLD( 14.0 + 2.0),	WORLD(-1.0),	WORLD(-8.0) )
};

////////////////////////////////////////////////////////////////////////////////
///////////////////////////////   Functions   //////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Fire guns.
 * Called when SPACE or joystick button is pressed.
 *****************************************************************************/
void
CombatFireGuns( Craft& craft, bool chase )
{
CHECK_TYPESIG( &craft, TYPESIG_CRAFT );

	// There are two sets of gun offsets: one for eye view, one for chase view.
	const uint         gunPortCnt = craft.GetGunCnt();
	const LocalVertex* gunPortArr = sGunPort1Eye;
	if ( gunPortCnt == 1 && chase )
	{
		gunPortArr = sGunPort1Chase;
	}
	else if ( gunPortCnt == 6 )
	{
		if ( chase )
			gunPortArr = sGunPort6Chase;
		else
			gunPortArr = sGunPort6Eye;
	}

	// Create a Tracer object for each gun.
	Dyna* dyna = &gEng.mEye;
	if ( chase ) dyna = &craft;
	for ( uint gun = 0; gun < gunPortCnt; ++gun )
	{
		new Tracer( *dyna, gunPortArr[gun] );
	}

	GFX_POST_REDISPLAY();
}
void
Listener_GameBase_JoystickButton( eJoyButton button )
{
	CombatFireGuns( *gGame.mPlayer, gEng.GetCurrent() != &gEng.mEye );
}

/*****************************************************************************
 * Draw an explosion.
 *****************************************************************************/
void
DrawExplosion( Dyna& dyna, int fireballCnt, fp fireballSize, fp offs )
{
CHECK_TYPESIG( &dyna, TYPESIG_DYNA );

	// Randomly place a few explosions around the Dyna's origin.
	for ( int i = 0; i < fireballCnt; ++i )
	{
		static fp texcoords_[8] = { 1, 0,
                                    1, 1,
                                    0, 1,
                                    0, 0 };
		static Texcoord8 texcoords = texcoords_;
		static RGBA rgba( 1.0, 1.0, 1.0, 0.8 );
		Position center( dyna.mCm[Ox] + random_f(offs) - offs*0.5,
                         dyna.mCm[Oy] + random_f(offs) - offs*0.5,
                         dyna.mCm[Oz] + random_f(offs) - offs*0.5 );
		if ( gWorld.IfBelowGround( center ) ) continue; // don't draw a fireball below ground
		TexBiquad texBiquad( center,
                             fireballSize,	// width
							 fireballSize,	// height
                             gEng.GetTexture( "explosion1.tga" ),
							 texcoords,
                             rgba );
		texBiquad.Draw();
	}
}

////////////////////////////////////////////////////////////////////////////////
///////////////////////////////   Tracer   /////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/// The tracer round is made of two long intersecting flat rectangles
/// for visibility from most viewpoints.
/// Local coords are normalized but scaled up by Draw().
const fp tracerLength	= WORLD(30.0);
const fp tracerWidth	= WORLD(0.5);
LocalVertex Tracer::mLocalVertexs[] =
{
	// vertical rectangle
    LocalVertex(  0*tracerWidth,     0*tracerWidth,		 0*tracerLength	),
    LocalVertex(  0*tracerWidth,     0*tracerWidth,		-1*tracerLength	),
    LocalVertex(  0*tracerWidth,     1*tracerWidth,		-1*tracerLength	),
    LocalVertex(  0*tracerWidth,     1*tracerWidth,		 0*tracerLength	),
	// horizontal rectangle
    LocalVertex(  1*tracerWidth,	0.5*tracerWidth,	 0*tracerLength	),
    LocalVertex(  1*tracerWidth,	0.5*tracerWidth,	-1*tracerLength	),
    LocalVertex( -1*tracerWidth,	0.5*tracerWidth,	-1*tracerLength	),
    LocalVertex( -1*tracerWidth,	0.5*tracerWidth,	 0*tracerLength	)
};

/*****************************************************************************
 * Constructor.
 * @param	dyna:
 *			Object that is firing its guns.
 * @param	gunPort:
 *			Where gunport is at (related to origin/center of model in local space).
 *****************************************************************************/
Tracer::Tracer( Dyna& dyna, const LocalVertex& gunPort )
:	Parent(dyna.GetQuadrant()),
	mGunner(dyna)
{
CHECK_TYPESIG( &dyna, TYPESIG_DYNA );

	GAME_STATS_CODE( ++gGame.mStatsBase.mTracers; )
	// modify some Dyna class members:
	mAnimate			= eAnimate_AUTO_TRANSLATE;
	mYmin				= 0.0;

	// Tracer class members:
	mFrameCntDown       = mFrameCntDownStart; // affects range of guns
	mMilsecCnt          = 0;
	mMilsecCntExplosion = 0;
	mLinkTracer         = gGame.mTracerDlist.Append( this );

	// Initialize matrix.
	// Either the eye or an craft object can fire.
	if ( &dyna == &gEng.mEye )
	{
		// The eye matrix maps world --> eye which is an inappropriate mapping
		// as the tracer model is in local coords.
		// What is needed is a matrix aligned similarly that maps local --> world.
		// The transpose of the eye matrix is what's needed.
		TransposeMatrix( mXm, dyna.mXm );	// transposing negates matrix origin
		SyncMatrix();
	}
	else
	{
		// A dyna matrix maps local --> world which is perfect.
		// In a non-1st-person view, the gun tracers are transformed using
		// a copy of the matrix of the Dyna object that fired its guns.
		CopyMatrix( mXm, dyna.mXm );
		SyncMatrix();
	}

	// Translate tracer to where gunport is (in model's local space).
	Translate( AXIS_X, gunPort.x );
	Translate( AXIS_Y, gunPort.y );
	Translate( AXIS_Z, gunPort.z );

	// Test if tracer round hit any other object (besides gunner).
	DetectHit();

	ASSERT( mYmin >= 0 ); // must be positive

	FinalConstruction();
	DEBUG_CODE( CheckObjectCtor(); )
}

/*****************************************************************************
 * Destructor.
 *****************************************************************************/
Tracer::~Tracer()
{
	GAME_STATS_CODE( --gGame.mStatsBase.mTracers; )
	DEBUG_CODE( CheckObjectDtor(); )
}

/*****************************************************************************
 * Stop tracer.
 *****************************************************************************/
void
Tracer::Stop( void )
{
	gGame.mTracerDlist.Unlink( mLinkTracer ); // remove from animation list
	SelfDestruct();
}

/*****************************************************************************
 * Test if tracer round hit any other object (besides gunner).
 *****************************************************************************/
bool
Tracer::DetectHit( void )
{
	// Hit craft?  Hit ground?  Hit an object?
	if (    CollisionDetectionCraft(     gGame.mCraftDlist )
         || CollisionDetectionDynaModel( gGame.mDynaModelTargetDlist )
         || gWorld.IfBelowGround( GetPosition() )
         || CollisionDetection( &mGunner ) )
	{
		// Skip exploding tracer if craft is too close.
		// Move the tracer backwards as CollisionDetection() will detect
		// a collision when the tracer has actually moved inside of another object.
		if ( DistanceSquaredMatrix( mGunner.mCm, mCm ) > WORLD(SQUARE(100.0)) )
		{
			Translate( AXIS_Z, -1.5 * mAxisDir[ZZ] * tracerLength );
			mAnimate = eAnimate_EXPLOSION;
		}
		else
		{
			Stop();
		}
		return true;
	}
	return false;
}

/*****************************************************************************
 * Return true if this tracer has hit another craft.
 *****************************************************************************/
bool
Tracer::CollisionDetectionCraft( Dlist<Craft>& dlist )
{
CHECK_TYPESIG( &dlist, TYPESIG_LIB_DLIST );
	for ( const Dlink<Craft>* dlink = dlist.ForStart(); dlist.ForTest(dlink); dlist.ForNext(dlink) )
	{
CHECK_TYPESIG( dlink, TYPESIG_LIB_DLINK );
		Craft* craft = dlink->mObj;
CHECK_TYPESIG( craft, TYPESIG_OBJECT );
		// Ignore tracers from the gunner itself.
		if ( UX(craft != &mGunner  &&  craft->Collided( *this )) )
		{
			craft->SetAnimation( eAnimate_COLLISION_SHOTDOWN_HARD );
			return true;
		}
	}
	return false;
}
// Similar to CollisionDetectionCraft().
bool
Tracer::CollisionDetectionDynaModel( Dlist<DynaModel>& dlist )
{
CHECK_TYPESIG( &dlist, TYPESIG_LIB_DLIST );
	for ( const Dlink<DynaModel>* dlink = dlist.ForStart(); dlist.ForTest(dlink); dlist.ForNext(dlink) )
	{
CHECK_TYPESIG( dlink, TYPESIG_LIB_DLINK );
		DynaModel* dyna = dlink->mObj;
CHECK_TYPESIG( dyna, TYPESIG_OBJECT );
		if ( UX(dyna->Collided( *this )) )
		{
			dyna->SetAnimation( eAnimate_COLLISION_SHOTDOWN_HARD );
			return true;
		}
	}
	return false;
}

/*****************************************************************************
 * Animation is driven by a timer.
 * See eng_dyna.hh about relationship of Animate() methods.
 *****************************************************************************/
int
Tracer::Animate( void )
{
	int redisplay = 0;

	const int milsec = gEng.GetMilsecFrame();	// time between current/previous timer-ticks
	--mFrameCntDown;
	mMilsecCnt += milsec;

	switch ( mAnimate )
	{
		// ----------------------------
		case eAnimate_AUTO_TRANSLATE:
		{
			// Eventually stop animating tracer bullets.
			if ( UX(mGunner.mAnimate == eAnimate_NONE  &&  mFrameCntDown == mFrameCntDownStart-7) )
			{
				// In the unusual case when the craft is stationary,
				// skip translation on a few frames so that tracers appear
				// to come directly from the gun ports and use has time to see them

				// (no code here)
			}
			else if ( EX(mFrameCntDown > 0) )
			{
				// Do as many translations as necessary (in one frame)
				// in order to literally not be faster than a speeding bullet
				// (keep the bullet object moving faster than the gunner object).
				fp stride = ABS( 3.0 * mGunner.GetStride() );
				//stride = Max<fp>( stride, tracerLength * 3.0 ); // tracerLength scaled-up to make bullets faster
				for ( fp i = 0.0; i < stride; i += tracerLength )
				{
					Translate( AXIS_Z, mAxisDir[ZZ] * tracerLength );
					// Test if tracer round hit any other object (besides gunner).
					if ( DetectHit() ) break;
				}
			}
			else // frameCntDown <= 0
			{
				Stop(); // it missed
			}
			redisplay = true;
		}
		break;
		// ----------------------------

		// ----------------------------
		case eAnimate_EXPLOSION:
		{
			if ( UX(mMilsecCnt - mMilsecCntExplosion > 500) )
				Stop();
			redisplay = true;
		}
		break;
		// ----------------------------

		default: // ignore
		break;
	}

	return redisplay;
}

/*****************************************************************************
 * Translate.
 *****************************************************************************/
void
Tracer::Translate( uint axis, fp inc )
{
	// Translate matrix.
	TranslateMatrixLocal( axis, (fpx)inc, mXm );
	SyncMatrix();
}

/*****************************************************************************
 * Draw.
 *****************************************************************************/
void
Tracer::Draw( void )
{
	if ( EX( mAnimate != eAnimate_EXPLOSION ) )
	{
		// The tracer round is made of two long intersecting flat rectangles
		// for visibility from most viewpoints.
		// Local coords are normalized but scaled up by Draw().
		const WorldVertex* worldVertexs = GetWorldVertexs();
		static const RGBA rgba( RGB_YELLOW, 1.0 );
		GFX_RGBA( rgba );
        uint vcnt = GetVertexCnt();
		for ( uint i = 0; i < vcnt; ++i )
		{
			if ( i == 0 || i == 4 ) GFX_BEGIN( GP_POLYGON );
			GFX_TRANSFORM_VERTEX( worldVertexs[i] );
			if ( i == 3 || i == 7 ) GFX_END();
		}
	}
	else
	{
		DrawExplosion( *this,
                       random_ui(3) + 5,
                       random_f(WORLD(5.0)) + WORLD(5.0),
                       WORLD(5.0) );
	}
}

////////////////////////////////////////////////////////////////////////////////
/////////////////////////////  DynaModelTarget  ////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Constructor.
 *****************************************************************************/
DynaModelTarget::DynaModelTarget( QuadrantPtrc q, const Position& pos, const vector<string>& fnameModelVec )
	: DynaModel(q,pos,fnameModelVec)
{
CHECK_TYPESIG( q, TYPESIG_QUADRANT );
	SetAnimation( eAnimate_AUTO_TRANSLATE );
	gGame.mDynaModelTargetDlist.Append( this );
	DEBUG_CODE( CheckObjectCtor(); )
}
DynaModelTarget::~DynaModelTarget()
{
	DEBUG_CODE( CheckObjectDtor(); )
}

/*****************************************************************************
 * Draw model.
 *****************************************************************************/
void
DynaModelTarget::Draw( void )
{
	Parent::Draw();
	if ( UX( IfDestroyed() ) )
	{
		DrawExplosion( *this,
		               random_ui(5) + 25,
			           Max( GetRadius() * 0.5, WORLD(15.0) ),
				       WORLD(15.0) );
	}
}

/*****************************************************************************
 * Animation is driven by a timer.
 * See eng_dyna.hh about relationship of Animate() methods.
 *****************************************************************************/
int
DynaModelTarget::Animate( void )
{
	int redisplay = 0;

	switch ( mAnimate )
	{
		case eAnimate_EXPLOSION:
		{
			redisplay = true;
		}
		break;

		case eAnimate_AUTO_TRANSLATE:
		{
			Translate( AXIS_X, WORLD(-10) );
			redisplay = true;
		}
		break;
	}

	return redisplay;
}
