/*****************************************************************************
 * $Id: game_base.cc,v 1.36 2006/08/10 03:18:58 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	Game classes.
 * @remarks	Some #includes are intentionally redundant in order for the
 *          to be noticed by makedepend.  Eg, game_class.hh includes
 *          game_base_class.hh but makedepend won't notice further levels.
 *          
 *          See game_base_init.cc about the distinctions between
 *          game construction, initialization, and starting.
 *//*
 * 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_CC 1
#include "game_base_common.hh"
#include "game_base_combat.hh"
#include "game_base_joystick.hh"
#include "game_base_craft.hh"
#include "game_config.hh"
#include "eng_dyna.hh"
#include "gfx_file.hh"

GameBaseEvents gGameBaseEvent;

/*****************************************************************************
 * Start new game.
 * Construction and starting of the game has to done at different times.
 *****************************************************************************/
void
Game::Start( void )
{
	// Order dependent.
	SelfChecks();
	gEng.mView.SetGeom( mDefaultWindowWidth, mDefaultWindowHeight );

	// Were any cmd-line args unrecognized?
ASSERT( mCmdargBase.mArgc > 0  &&  mCmdargBase.mArgv0 != "" );
	for ( int i = 1; i < mCmdargBase.mArgc; ++i )
	{
		if ( ! IfCmdargDone( i ) )
			*gStreamOut << "unrecognized cmd-line arg: " << mCmdargBase.mArgv[i] << endl;
	}
}
void
Game::End( void )
{
	StartStopListeners( false );
#if DEBUG
	EndDebug();
#endif
}

/*****************************************************************************
 * Self-checks.  Causes program exit if any fail.
 *****************************************************************************/
void
Game::SelfChecks( void )
{
	// Not a good check as game-specific typesigs can't be checked at this level.
	ASSERT( TYPESIG_AFTER_GAME_BASE < TYPESIG_MAX ); // catch if typesig value is too big
}

/*****************************************************************************
 * Game module animation routine.
 * Called periodically by a timer (by the engine's Animate()).
 * @return True if redisplay is necessary.
 *****************************************************************************/
int
Game::Animate( void )
{
	int redisplay = 0;

	// If keyboard/joystick input demands redisplaying.
	if ( gEng.mView.IfRedisplayInput() ) { ++redisplay; gEng.mView.ResetRedisplayInput(); }

	// Animate dynamic objects.
	redisplay |= AnimateList<Craft>( mCraftDlist );
	redisplay |= AnimateList<DynaModel>( mDynaModelTargetDlist );
	redisplay |= AnimateList<Tracer>( mTracerDlist );

	// Lastly, animate the eye.
	redisplay |= gEng.mEye.Animate();

	return redisplay;
}

/*****************************************************************************
 * Every timer tick.
 * Called periodically by a timer (by the engine's Tick()).
 *****************************************************************************/
void
Game::Tick( void )
{
	// The amount of time since the last timer interrupt.
	const int milsec = gEng.GetMilsecFrame();

	// Call InputQueue::Tick() periodically (to feed the input queue).
	// Then poll the InputQueue for events.
	static int milsecInputQueueCountdown = INPUT_QUEUE_POLL_TIMER_MILSEC;
	milsecInputQueueCountdown -= milsec;
	if ( milsecInputQueueCountdown <= 0 )
	{
		RESET_COUNTDOWN( milsecInputQueueCountdown, INPUT_QUEUE_POLL_TIMER_MILSEC );
		mInputQueue.Tick( milsec );

		// Don't process input events while rendering.
		// Eg, moving the joystick while an enemy view is being
		// rendered would affect an enemy object, not the player object.
		if ( ! gEng.mView.IfBusy() )
		{
			PollInput();
		}
	}
}

/*****************************************************************************
 * Poll all input event queues.
 *****************************************************************************/
void
Game::PollInput( void )
{
	PollKeyboard();
	gJoy.PollJoystick();

	// Begin dropping events if an event queue is growing too quickly.
	// The joystick is more prone to causing this problem than the keyboard.
	while ( mInputQueue.KeyEvents() > 10 )
	{
		KeyEvent event;
		mInputQueue.GetKeyEvent( &event ); // drop oldest keyboard event
	}
	while ( mInputQueue.JoyEvents() > 10 )
	{
		JoyEvent event;
		mInputQueue.GetJoyEvent( &event ); // drop oldest joystick event
	}
}

/*****************************************************************************
 * Return player's matrix (read-only).
 *****************************************************************************/
const Matrix&
Game::GetPlayerMatrix( void )
{
	// Player can be flying in first-person or chase view.
	if ( gEng.GetCurrent() == &gEng.mEye )
		return gEng.mEye.mCm;
	else
		return GetPlayerCraft()->mCm;
}

/*****************************************************************************
 * Functions to manage change response/sensitivity of flight controls.
 * Two response values are used: one for 1st-person flight mode, another for
 * for chase plane mode.
 *****************************************************************************/
Percent
Game::GetControlResponse( void )
{
	if ( gEng.GetCurrent() == &gEng.mEye )
		return mControlResponseEye;
	else
		return mControlResponseChase;
}
// dir(ection) should be +1 or -1 (nothing else).
void
Game::IncControlResponse( int dir )
{
	const int inc   = 10 * dir;
	const int limit = 100;
	Percent* r = (gEng.GetCurrent()==&gEng.mEye) ? &mControlResponseEye : &mControlResponseChase;

	// Increment value.
	*r += inc;
	if ( *r < 0 )     *r = 0; // 0 means default, not 0%
	if ( *r > limit ) *r = limit;

	// Change appropriate Dyna object.
	if ( gEng.GetCurrent() == &gEng.mEye )
	{
		SetControlResponse( gEng.mEye, *r );
	}
	else
	{
		SetControlResponse( *mPlayer1Craft, *r );
		SetControlResponse( *mPlayer2Craft, *r );
	}
}
void
Game::SetControlResponse( Dyna& dyna, Percent n )
{
	if ( &dyna == &gEng.mEye )
		mControlResponseEye = n;
	else
		mControlResponseChase = n;

	// 50% means normal/median responsiveness.
	if ( n > 0 )
	{
		if ( n < 100 )
			dyna.SetResponse( n / 50.0 );
		else
			dyna.SetResponse( n / 10.0 ); // 100% is super-fast
	}
	else
	{
		dyna.SetResponse( 1.0 );
	}

	switch ( n/10 )
	{
		case 0:
			// 0 means default, not 0%
			gJoy.DefaultJoystickResponse();
			break;
		case 1:
			gJoy.ScaleJoystickResponse( 1.00, 1.00, 1.00 );
			break;
		case 2:
			gJoy.ScaleJoystickResponse( 1.33, 1.33, 1.33 );
			break;
		case 3:
			gJoy.ScaleJoystickResponse( 1.66, 1.66, 1.66 );
			break;
		case 4:
			gJoy.ScaleJoystickResponse( 2.33, 2.33, 2.33 );
			break;
		case 5:
			gJoy.ScaleJoystickResponse( 2.66, 2.66, 2.66 );
			break;
		case 6:
			gJoy.ScaleJoystickResponse( 3.33, 3.33, 3.33 );
			break;
		case 7:
			gJoy.ScaleJoystickResponse( 3.66, 3.66, 3.66 );
			break;
		case 8:
			gJoy.ScaleJoystickResponse( 4.33, 4.33, 4.33 );
			break;
		case 9:
			gJoy.ScaleJoystickResponse( 4.66, 4.66, 4.66 );
			break;
		case 10:
			gJoy.ScaleJoystickResponse( 5.33, 5.33, 5.33 );
			break;
		default:
			// nothing
			break;
	}
}

/*****************************************************************************
 * Pathname wrappers.
 * Game base class doesn't know the executable name, so pass it.
 *****************************************************************************/
static string sInstallDir;
string
Game::GetInstallDir( const string& execName, char** argv )
{
	// Already computed the path?
	if ( sInstallDir.empty() )
	{
		// argv[0] may contain either an absolute or relative path.
		// Sudo-code:
		// if abspath($argv[0])
		//		$imgDir =         Remove($argv[0],$execName) + "../img/" + $filename
		//	 else
		//		$imgDir = cwd() + Remove($argv[0],$execName) + "../img/" + $filename
		if ( IfAbsPath( argv[0] ) )
		{
			sInstallDir = argv[0];
		}
		else
		{
			if ( mCwd.empty() )
			{
				*gStreamError << "ERROR: cwd failed" << endl;
				sInstallDir.erase();
				return sInstallDir;
			}
			sInstallDir  = mCwd;
			sInstallDir += DIRECTORY_CHAR_STR;
			sInstallDir += argv[0];
		}

		// Cut out executable name.
		sInstallDir.erase( sInstallDir.size() - execName.size() );
	}
	return sInstallDir;
}
