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

#define GAME_PONY_WORLD_CC 1
#include "game_pony_common.hh"
#include "game_pony_world.hh"
#include "game_pony_init.hh"
#include "game_pony_buildings.hh"
#include "game_pony_sky.hh"
#include "game_pony_terrain.hh"
#include "game_pony_water.hh"
#include "eng_world.hh"
#include "gfx_random.hh"

extern Ptr<Runway> gRunway;

/*****************************************************************************
 * Make runway at center of world.
 *****************************************************************************/
static void
MakeRunway( void )
{
	// Make Runway only once.  It persists as Runway::Reproducible() is false.
	if ( gRunway == NULL )
	{
		ASSERT_RUN_ONCE;

	    Position posRunway = gWorld.GetCenter();
	    posRunway.z -= Runway::DefaultLength();
		gRunway = new Runway( gWorld.GetCenterQuadrant(), posRunway );
	}
}

/*****************************************************************************
 * See World::Make1().
 *****************************************************************************/
void
GamePonyWorld::Make( void )
{
	// -------------------------------------------------------------------------
	// ------- members not yet initialized (gWorld unavailable) ----------------
	// -------------------------------------------------------------------------

	// Preparations prior to World::Make().
	if ( ! gEng.IfQuiet() )
	{
		Splash();
		*gStreamOut << "Making the simulated world..." << endl;
	}
	switch ( gWorldParm.mWorldSize )
	{
		case eWorldSize_NORMAL: default:
		gWorldParm.mWorldDim[XX] = gWorldParm.mWorldDim[ZZ] = 24;
		break;

		case eWorldSize_LARGE:
		gWorldParm.mWorldDim[XX] = gWorldParm.mWorldDim[ZZ] = 32;
		break;

		case eWorldSize_HUGE:
		gWorldParm.mWorldDim[XX] = gWorldParm.mWorldDim[ZZ] = 48;
		break;
	}

	// -------------------------------------------------------------------------
	// -------------------- Make the World in phases ---------------------------
	// -------------------------------------------------------------------------

	// First phase:
	//
	// Call World::Make1() which sets the dimensions of the world.
	Array<3> dimLocus;
	dimLocus[XX] = 5;
	dimLocus[YY] = 1;
	dimLocus[ZZ] = 5;
	Parent::Make1( gWorldParm.mWorldDim,
                   gWorldParm.mQuadrantEdge,
				   gWorldParm.mMargin,
                   dimLocus );

	// Intermediate phase:

	// For Object::Visible().
ASSERT( mQuadrantEdge > 0.0 );
	SetObjectSmallDist( WORLD(15000.0) );
	SetObjectLargeDist( WORLD( mQuadrantEdge * ((mDimLocus[XX]>>1)+1) ) );
	gEng.SetLodUser( gEng.GetLodUser() );	// changes mObjectSmallDist2

	// Final phase:
	//
	// Call World::Make2() which constructs and populates the Quadrants.
	// Quadrant::Quadrant() calls the virtual World::PopulateQuadrant()
	// to populate the quadrant specifically for a game module.
	Parent::Make2();
	mMade = true;

    // GetCenter().
    // Base World inappropriately places center of world halfway into the sky,
    // so change center.y to floor.
    mCenter.y = GROUND_LEVEL;
    const Position& center = mCenter;

	// -------------------------------------------------------------------------
	// ------------------ World is made (gWorld is available) ------------------
	// -------------------------------------------------------------------------

    // Make runway at center of world.
    MakeRunway();

	// Splash with a count of objects in the locus.
	// No total object count is available as Quadrants outside the Locus are empty.
	mLocus->Update( center );
	if ( ! gEng.IfQuiet() )
	{
		*gEng.mStreamStatus
			<< "Made [" << mDimQuadrant[XX] << "]["
	                    << mDimQuadrant[YY] << "]["
	                    << mDimQuadrant[ZZ] << "] "
	        << "quadrants " << "(" << mObjectCnt << " objects in locus). \n";
	}

	// Move Eye to center.
	Position posEye = center;
	posEye.y += WORLD(20.0);
    posEye.z -= gRunway->Length() * 0.5;
	gEng.mEye.Init( GetCenterQuadrant(), posEye );
}

/*****************************************************************************
 * Called by Quadrant::Populate() as it defers to an outside module.
 *****************************************************************************/
void
GamePonyWorld::PopulateQuadrant( QuadrantPtrc q )
{
	// Reload the same random seed to generate the same sequence of psuedo random numbers
	// in order to reproduce randomly-constructed objects the same way.
	randomize( gEng.GetSeed() );

	// - - - - - - - - - - - - - - - - -
	// Non-reproducible objects
	// - - - - - - - - - - - - - - - - -
	if ( q->GetState() == eQuadrantState_EMPTY )
	{
		// Quadrant enters the shrunk state when it has only non-reproducible objects
		q->SetState( eQuadrantState_SHRUNK );

		new Sky( q ); // see doc/issues.txt
	}

	// - - - - - - - - - - - - - - - - -
	// Reproducible objects
	// - - - - - - - - - - - - - - - - -
	if ( q->GetState() != eQuadrantState_POPULATED )
	{
		q->SetState( eQuadrantState_POPULATED );

		new Terrain( q, Ptr<Water>( new Water(q) ) );

		// Add nothing more to edge quadrants.
		// Edge quadrants should be (mostly) empty to avoid objects magically/annoyingly
		// appearing when the viewpoint wraps around the world.
		if ( ! IfEdgeQuadrant( q ) )
		{
			// Center quadrant is a special case.
			if ( IfCenterQuadrant( q ) )
			{
                MakeRunway();
			}
		}
	}
}

/*****************************************************************************
 * Keep a Dyna within the world.
 * See World::Dyna().
 * @param	dyna
 * @param	pos (IN/OUT)
 *			Position object which might be corrected.
 *          The Dyna class calls World::Fence().
 *          World::Fence() defers to the Dyna class its position.
 *****************************************************************************/
bool
GamePonyWorld::Fence( Dyna& dyna, Position& pos /*IN/OUT*/ )
{
	// The base World::Dyna() wraps at the edges of the world.
	// But for this game, altitude (Y) should be clamped, not wrapped.
	if ( pos.y > mDimWorld[YY] )
	{
		// Dyna flew too high in the (n,1,n) world.
		// Still call base Fence() to check X,Z dimensions.
		pos.y = mDimWorld[YY];
		Parent::Fence( dyna, pos );
		return true;
	}
	else if ( pos.y < 0.0 )
	{
		// Likewise for low altitude.
		pos.y = 0.0;
		Parent::Fence( dyna, pos );
		return true;
	}
	else
	{
		return Parent::Fence( dyna, pos );
	}
}

/*****************************************************************************
 * Returns true is an object's position/radius lies too close to the initial
 * viewpoint.  Eg, don't want the program to startup inside of a mountain.
 *****************************************************************************/
bool
GamePonyWorld::TooCloseToInitialViewpoint( const Posrad& posrad ) const
{
	return gRunway->IfOverlaps( posrad );  // not IfInside()
}

/*****************************************************************************
 * Called when LOD was changed.
 *****************************************************************************/
void
GamePonyWorld::LodUserChanged( void )
{
	// mObjectSmallDist2 is used by Object::Visible().
	// Only the World class really knows how far away a small object becomes invisible.
	// Eg, trees will be shown even if a few pixels wide if max LOD.
	switch ( gEng.GetLodUser() )
	{
		case eLodUser_MAX:
		SetObjectSmallDist( WORLD(55000.0) );
		break;

		case eLodUser_HIGH:
		SetObjectSmallDist( WORLD(30000.0) );
		break;

		default:
		SetObjectSmallDist( WORLD(15000.0) );
		break;

		case eLodUser_MIN: case eLodUser_LOW:
		SetObjectSmallDist( WORLD(7500.0) );
		break;
	}
}
