// Gameballworld.cpp: implementation of the GameBallworld class and associated classes.
//
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "gameballworld.h"
#include "force.h"
#include "listener.h"
#include "pop.h" //for playSound
#include "spritepolygon.h" 
#include "critterviewer.h"
#include "graphicsopengl.h"
#include "graphicsmfc.h"
#include "critterwall.h"

IMPLEMENT_SERIAL( cGameBallworld, cGame, 0);
IMPLEMENT_SERIAL( cCritterBallProp, cCritter, 0);
IMPLEMENT_SERIAL( cCritterFunThing, cCritter, 0);
IMPLEMENT_SERIAL( cCritterBallworldPlayer, cCritterArmedPlayer, 0);
IMPLEMENT_SERIAL( cCritterTreasure, cCritter, 0);
IMPLEMENT_SERIAL( cCritterAddAmmo, cCritter, 0);

#define HANDSETVIEWERPOS
	/* HANDSETVIEWERPOS turns on the option of hand setting the viewer pos in 
the InitializeViewer call.  It's more standard to comment this out, but I had some
lingering problems with the default approach for a long thin world, so now I'm preferring
the HANDSETVIEWERPOS optoin. The other way WORKS, but I don't TRUST it anymore for this
paritcualr world as I have so often seen it arbitrarily put my view BEHIND the world 
(negative z), though this coudl have been due to some cCritterViewer trackplayer code
thats' been recently revised.  So you can probably comment out HANDSETVIEWERPOS.*/

//==============================cGameBallworld=======
Real cGameBallworld::TREASURERADIUS = 1.2;
Real cGameBallworld::WALLTHICKNESS = 0.2;

cGameBallworld::cGameBallworld()
{
//Fix the menu selections you'll allow.
	_menuflags &= ~cGame::MENU_BOUNCEWRAP; 
		/* Default _menuflags from cGame has autoplay off, hopper off. 
		Turn off wrap because it's bad with gravity, we want balls bouncing on bottom.*/
//	_menuflags &= ~cGame::MENU_3D; 
		/* Turn off 3D option because it's bad with the long thin background bitmap
		called IDB_STREET.  Other than that, though, 3D is ok for this game. */
	_menuflags &= ~cGame::MENU_TRACKPLAYER;
		/* Don't allow user to turn off the track player option. */ 
	_menuflags &= ~cGame::MENU_RIDEPLAYER;
		/* Don't ride the player */
	_menuflags |= cGame::MENU_HOPPER; //Turn on hopper listener option.
	_menuflags &= ~cGame::MENU_CHANGELISTENER; 
		/* It looks bad to use any listener other than hopper. */
	_spritetype = cGame::ST_BUBBLES;
	_border.set(68.3, 17.1); //Use a less long, thin world.
//	_border.matchAspect(1024.0,256.0);
	for(int i = 0; i<25; ++i)
	{
		walls[i] = NULL;
	}
	level = 0;
	nextLevel();
		/*  Sets the cSpriteBackground for the desired background bitmap.
		Note that you must call this after the _border has its size fully initialized. So each
		cGame child with a different border should call this again in its constructor. */
	setWrapflag(cCritter::BOUNCE);
	_seedcount = 7; 
	setPlayer(new cCritterBallworldPlayer(this));

	new cCritterFunMeter(cVector(-20,5), cVector(-18,5), cGameBallworld::WALLTHICKNESS, this); 

	_ptreasure = new cCritterTreasure(this);
	//_arrayHCURSOR has only the _hCursorArrow inherited from cGame.
	//_arrayHCURSOR.Add(((CPopApp*)::AfxGetApp())->_hCursorPlay); //No need for the shoot cursor
}

void cGameBallworld::initializeView(CPopView *pview)
{
	cGame::initializeView(pview);
	pview->setUseBackgroundBitmap(TRUE);
//	pview->setGraphicsClass(RUNTIME_CLASS(cGraphicsOpenGL)); //Comment this in for a 3D start
	pview->pviewpointcritter()->setTrackplayer(TRUE); //Always look at the player.
	pview->pviewpointcritter()->setFoveaProportion(.8); /* A critterviewer has a _foveaproortion
		that ranges from 0.0 to 1.0.  We make it smaller than the default value of 0.85 so
		that the trackplayer image will scroll before player gets all the way to edge. 
		Reduce still more if you want to scroll even sooner.*/
}

void cGameBallworld::initializeViewpoint(cCritterViewer *pviewer)
{
	if (pviewer->is3D())
	{
#ifdef HANDSETVIEWERPOS
		pviewer->setViewpoint(cVector3(0.0, -8.0, 40.0), 
			cVector(border().lox(), border().midy()),
			FALSE);  
			/* The setViewpoint args are (directiontoviewer, lookatpoint, trytoseewholeworld),
			the default third argument is TRUE, which means to back off enough to
			see the whole world.  But in a long thin world, we don't want to do that.  */
#else //HANDSETVIEWERPOS
		pviewer->setViewpoint(cVector3(0.0, -0.2, 1.0), 
			cVector(border().lox(), border().midy()));  /* the default third TRUE argument
			will move the viewpoint far from the world and try to see it all. */
		pviewer->zoom(7.0); /* The bigger the world and the further away you are, 
			the more zoom you need. */
		/* I don't like this way as it makes the player trackign a little flaky to be in a
		high zoom. */
#endif //HANDSETVIEWERPOS
	}
	else //2D case
	{
		pviewer->setViewpoint(cVector::ZAXIS, //pplayer()->position());
			cVector(border().lox(), border().midy()));
		pviewer->zoom(2.25);
	}
}

void cGameBallworld::nextLevel()
{
	++level;
	for(int i = 0; i < 25; ++i)
	{
		if(walls[i])
			walls[i]->delete_me();
		walls[i] = NULL;
	}

	switch(level)
	{
	case 1:
		setBackgroundBitmap(IDB_BITMAP1); 
		walls[0] = new cCritterWall( cVector(-20, -5),  cVector(-12, -5), cGameBallworld::WALLTHICKNESS, this);
		walls[1] = new cCritterWall( cVector(-4, -5),  cVector(4, -5), cGameBallworld::WALLTHICKNESS, this);
		walls[2] = new cCritterWall( cVector(10, -5),  cVector(15, -5), cGameBallworld::WALLTHICKNESS, this);
		walls[3] = new cCritterWall( cVector(20, -5),  cVector(30, -5), cGameBallworld::WALLTHICKNESS, this);

		walls[4] = new cCritterWall( cVector(-30 ,  0),  cVector(-25,  0), cGameBallworld::WALLTHICKNESS, this);
		walls[5] = new cCritterWall( cVector(-22 ,  4),  cVector(0,  4), cGameBallworld::WALLTHICKNESS, this);
		walls[6] = new cCritterWall( cVector(-4 ,  5),  cVector(10,  5), cGameBallworld::WALLTHICKNESS, this);
		walls[7] = new cCritterWall( cVector(12 ,  3),  cVector(24,  3), cGameBallworld::WALLTHICKNESS, this);
		walls[8] = new cCritterFunZone( cVector(26 ,  4),  cVector(29,  4), cGameBallworld::WALLTHICKNESS, this, 15);
		break;
	case 2:
		setBackgroundBitmap(IDB_STREET);
		walls[0] = new cCritterWall( cVector(-23,4), cVector(-5, 20), cGameBallworld::WALLTHICKNESS, this);
		break;
	}
}
void cGameBallworld::seedCritters()
{
//	pbiota()->purgeCritters(RUNTIME_CLASS(cCritterBullet));
//	pbiota()->purgeCritters(RUNTIME_CLASS(cCritterBallProp));
//	pbiota()->purgeCritters(RUNTIME_CLASS(cCritterFunThing));
	int enemycount = pbiota()->count(RUNTIME_CLASS(cCritterBallProp));
	int itemcount = pbiota()->count(RUNTIME_CLASS(cCritterFunThing));
	int nerdguncount = pbiota()->count(RUNTIME_CLASS(cCritterAddAmmo));
	for (int i=0; i<_seedcount - enemycount; i++)
		new cCritterBallProp(this);
	for (int i=0; i<5 - itemcount; i++)
		new cCritterFunThing(this);
	for (int i=0; i<1 - nerdguncount; i++)
		new cCritterAddAmmo(this);
	pplayer()->moveTo(cVector(_border.lox()+5, _border.loy()));
}

void cGameBallworld::adjustGameParameters()
{
// (1) End the game if the player is dead
	if (!health()  && !_gameover) //Player's been killed and game's not over.
	{
		_gameover = TRUE;
		pplayer()->addScore(_scorecorrection); // So user can reach _maxscore 
		playSound("Tada");
		return; 
	}
// (2) Also don't let the ballcount diminish.
			//(need to recheck propcount in case we just called seedCritters).
	int ballcount = pbiota()->count(RUNTIME_CLASS(cCritterBallProp));
	int ballstoadd = _seedcount - ballcount;
 	for (int i=0; i < ballstoadd; i++)
		new cCritterBallProp(this);
// (3) Maybe check some other conditions.
 }

CString cGameBallworld::statusMessage()
{
	CString cStrStatusBar;
	int nUpdatesPerSecond;
	CString cStrUpdatespersecond;
	CString cStrCount;
	CString cStrScore;
	CString cStrHealth;
	CString cStrAmmo;

	if (!gamepaused())
	{
		nUpdatesPerSecond = int(((CPopApp*)::AfxGetApp())->_timer.updatesPerSecond());
		if (!nUpdatesPerSecond)
			cStrUpdatespersecond.Format("Less than one update per second.");
		else	
			cStrUpdatespersecond.Format("Updates per second: %d.", nUpdatesPerSecond);
		if (((CPopApp*)::AfxGetApp())->_timer.runningNearMinSpeed())
			cStrUpdatespersecond += " (Slower than Real Time)";
	}
	else
			cStrUpdatespersecond.Format("Animation is paused.");
	cStrScore.Format("Score: %d.", score());
	cStrHealth.Format("Fun: %d.", health());
	int crittercount = _pbiota->count(RUNTIME_CLASS(cCritter));
	int bulletcount = _pbiota->count(RUNTIME_CLASS(cCritterBullet));
	cStrAmmo.Format("Ammo: %d.", ((cCritterBallworldPlayer*)pplayer())->ammo);
	//cStrCount.Format("Ball Critters: %d.", crittercount-bulletcount-2); //Subtract two for player and treasure
	cStrStatusBar = cStrScore + cStrHealth +  + cStrAmmo + "  " + cStrUpdatespersecond; 
	return cStrStatusBar;
}

void cGameBallworld::Serialize(CArchive& ar) /* This is a bit of work, as I have to match
	_ppuck , _phockeyrobot , _pmygoal , _probotgoal to the pointers in the cBiota array. */
{
	int treasureindex;
	cGame::Serialize(ar);
	if (ar.IsStoring()) // Save 
 	{
		ar << _index(_ptreasure);
	}
	else //Load
	{
		ar >> treasureindex;
			// I have to cast from cCritter*
		_ptreasure = (cCritterTreasure*)(_pbiota->GetAt(treasureindex));
	}
}
//====================Critters for the Ballworld Game ==========================
//==================================================================


//==================cCritterBallworldPlayer=============

cCritterBallworldPlayer::cCritterBallworldPlayer(cGame *pownergame):
cCritterArmedPlayer(pownergame)
{
	ammo = 10;
	setHealth(10);
	moveTo(_movebox.locorner());
	setWrapflag(cCritter::CLAMP); //Use CLAMP so you stop dead at edges.
	setArmed(TRUE);  //Let's not use bullets.
	setMaxspeed(40.0); //Need good speed to overcome gravity.
	setListener(new cListenerHopper(6.0, 0.2, 0.8)); /* The three arguments to the
		cListenerHopper constructor specify the walkspeed, the hopuptime, and the hopagainwait.
		Walkspeed is how fast the left/right (and PgUp/PgDn if in 3D where the ground is xz plane)
			arrow keys move the critter,
		Hopuptime is how many seconds you can accelerate up with the UP arrow, and
		Hopupwait is how many seconds you have to wait after the end of one hop
			before starting a new hop. */
	setListenerAcceleration(160.0); //So Hopper can overcome gravity
	addForce(new cForceGravity(50.0)); /* Uses  gravity. Default strength is 25.0.
		Gravity	will affect player using cListenerHopper. */
//	addForce(new cForceDrag(0.5)); //Better without friction.
//	setDensity(8.0 * density()); //To make it fall faster, but we don't need this.
	setRadius(1.0); //Default cCritter::PLAYERRADIUS is 0.4. 
	setAttitudeToMotionLock(FALSE);  //It looks nicer is you don't turn the player with motion.
	setAttitudeTangent(cVector::YAXIS); /* Without motion lock, we need setAttitudeTangent 
		to make sure he's pointing up at the start. */
}

cCritterBullet* cCritterBallworldPlayer::shoot()
{
	if(ammo>0)
	{
		cCritterBullet *pbullet = cCritterArmed::shoot();
		--ammo;
		return pbullet;
	}
	return NULL;
}

BOOL cCritterBallworldPlayer::collide(cCritter *pcritter)
{
	BOOL playerhigherthancritter = position().y() - radius() > pcritter->position().y();
/* If you are "higher" than the pcritter, as in jumping on it, you get a poitn
and the critter dies.  If you are lower than it, you lose health and the
critter also dies. To be higher, let's say your low point has to higher
than the critter's center. We compute playerhigherthancritter before the collide,
as collide can change the positions. */
	BOOL collided = cCritter::collide(pcritter);
	if (!collided)
		return FALSE;
/* If you're here, you collided.  We'll treat all the guys the same -- the collision
 with a Treasure is different, but we let the Treasure contol that collision. */
	/*if (playerhigherthancritter)
	{
		playSound("Ding");
		addScore(10);
	}
	else
		damage(1);
		*/
	pcritter->die();
	return TRUE;
}


/*
BOOL cCritterBallworldPlayer::collide(cCritterBallProp *pcritter)
{
	BOOL collided = cCritter::collide(pcritter);
	if(!collided)
		return FALSE;
	damage(2);
	pcritter->die();
}

BOOL cCritterBallworldPlayer::collide(cCritterFunThing *pcritter)
{
	BOOL collided = cCritter::collide(pcritter);
	if(!collided)
		return false;
	damage(-5);
	pcritter->die();
}
*/
//==================cCritterBallProp=============

cCritterBallProp::cCritterBallProp(cGame *pownergame):
cCritter(pownergame)
{
	Real type = cRandomizer::pinstance()->randomReal(0,5);
	if(type <= 2)
	{
		if (pgame())
			setSprite(new cSpriteIcon(IDB_BITMAP3));
		setRadius(.5);
		damage = 1;
	}
	else if(type <= 3)
	{
		if (pgame())
			setSprite(new cSpriteIcon(IDB_BITMAP6));
		setRadius(.8);
		setHealth(2);
		damage = 2;
	}
	else if(type <= 4)
	{
		if (pgame())
			setSprite(new cSpriteIcon(IDB_BITMAP7));
		setRadius(1);
		setHealth(3);
		damage = 3;
	}
	else 
	{
		if (pgame())
			setSprite(new cSpriteIcon(IDB_BITMAP8));
		setRadius(1.5);
		setHealth(4);
		damage = 5;
	}
	
	_collidepriority = cCollider::CP_PLAYER + 1;
	addForce(new cForceGravity(25.0, cVector( 0, -1, 0.0)));
	addForce(new cForceObjectSeek( pownergame->pplayer(), 2.0));
	//addForce(new cForceDrag()); //Uses default friction strength 0.5.
	setDensity(2.0);
	setMaxspeed(50.0);
   	randomize(cCritter::MF_VELOCITY );
	setBounciness(2); //less than 1.0 means it loses a bit of energy with each bounce.
	setMinTwitchThresholdSpeed(4.0); //Means sprite doesn't switch direction unless it's moving fast
	Real playerx = 0.0;
	Real playery = 0.0;
	if (pownergame)
	{
		playerx = pownergame->pplayer()->position().x();
		playery = pownergame->pplayer()->position().x();
	}
	
	Real vel = cRandomizer::pinstance()->randomReal(0, 2);
	if(vel<1)
		randomizePosition(cRealBox(cVector(playerx + 10, playery + 10),_movebox.hicorner()));
		//randomizePosition(cRealBox(cVector(_movebox.hix()-.01, _movebox.loy()), _movebox.hicorner()));				// anywhere on screen above the player
	else
		randomizePosition(cRealBox(_movebox.locorner(), cVector(playerx - 10, playery + 10)));
		//randomizePosition(cRealBox(_movebox.locorner(), cVector(_movebox.lox()+.01, _movebox.hiy())));
	
}

void cCritterBallProp::die()
{
	cCritter::die();
}

void cCritterBallProp::update(CPopView *pactiveview, Real dt)
{
	cCritter::update(pactiveview, dt); //Always call this first
	if (_outcode & BOX_LOX) /* use bitwise AND to check if a flag is set. */
		delete_me(); //tell the game to remove yourself
}


BOOL cCritterBallProp::collide(cCritter *pcritter)
{
	BOOL collided = cCritter::collide(pcritter);
	if (collided) 
	{
		if (pcritter->IsKindOf(RUNTIME_CLASS(cCritterBallworldPlayer)))	
		{
			pcritter->damage(damage);
			die();
			return TRUE;
		}
		if(pcritter->IsKindOf(RUNTIME_CLASS(cCritterWall)))
			return TRUE;
	}
	return FALSE;
}
//==================cCritterFunThing=============

cCritterFunThing::cCritterFunThing(cGame *pownergame):
cCritter(pownergame)
{
	Real type = cRandomizer::pinstance()->randomReal(0,5);
	if(type <= 2)
	{
		if (pgame())
			setSprite(new cSpriteIcon(IDB_BITMAP9));
		setRadius(.2);
		bonus = 1;
	}
	else if(type <= 3)
	{
		if (pgame())
			setSprite(new cSpriteIcon(IDB_BITMAP12));
		setRadius(.3);
		bonus = 2;
	}
	else if(type <= 4)
	{
		if (pgame())
			setSprite(new cSpriteIcon(IDB_BITMAP10));
		setRadius(1);
		bonus = 3;
	}
	else 
	{
		if (pgame())
			setSprite(new cSpriteIcon(IDB_BITMAP4));
		setRadius(1.5);
		bonus = 5;
	}
		//setSprite(pgame()->randomSprite(pgame()->spritetype()));
	_collidepriority = cCollider::CP_PLAYER + 1; /* Let this guy call collide on the
		player, as his method is overloaded in a special way. */
	addForce(new cForceGravity(25.0, cVector( 0, -1, 0.0))); /* Make the
		gravity tilt towards the left to move them along. */
	//addForce(new cForceDrag()); //Uses default friction strength 0.5.
	//randomize(cSprite::MF_RADIUS);
	setRadius(1.0);
	setDensity(10);
	setMaxspeed(10);
   	randomize(cCritter::MF_VELOCITY );//| cSprite::MF_RADIUS);
	setBounciness(-.5); //so they hit solid ground and stay there
	setMinTwitchThresholdSpeed(4.0); //Means sprite doesn't switch direction unless it's moving fast
	Real playerx = 0.0;
	if (pownergame)
		playerx = pownergame->pplayer()->position().x();
	randomizePosition(cRealBox(_movebox.locorner(),	// anywhere on screen
		_movebox.hicorner()));
}

void cCritterFunThing::die()
{
	playSound("Ding");
	cCritter::die();
}

void cCritterFunThing::update(CPopView *pactiveview, Real dt)
{
	cCritter::update(pactiveview, dt); //Always call this first
	if (_outcode & BOX_LOX) /* use bitwise AND to check if a flag is set. */
		delete_me(); //tell the game to remove yourself
}

BOOL cCritterFunThing::collide(cCritter *pcritter)
{
	BOOL collided = cCritter::collide(pcritter);
	if (collided)
	{
		if( (pcritter->IsKindOf(RUNTIME_CLASS(cCritterBallworldPlayer))))	
		{
			pcritter->addHealth(bonus);
			die();
			return TRUE;
		}
		else if (pcritter->IsKindOf(RUNTIME_CLASS(cCritterWall)))
			return TRUE;
	}
	return FALSE;
}
//==================cCritterAddAmmo=============

cCritterAddAmmo::cCritterAddAmmo(cGame *pownergame):
cCritter(pownergame)
{
	if (pgame())
		setSprite(new cSpriteIcon(IDB_BITMAP11));
		//setSprite(pgame()->randomSprite(pgame()->spritetype()));
	_collidepriority = cCollider::CP_PLAYER + 1; /* Let this guy call collide on the
		player, as his method is overloaded in a special way. */
	addForce(new cForceGravity(25.0, cVector( 0, -1, 0.0))); /* Make the
		gravity tilt towards the left to move them along. */
	//addForce(new cForceDrag()); //Uses default friction strength 0.5.
	//randomize(cSprite::MF_RADIUS);
	setRadius(1.0);
	setDensity(10);
	setMaxspeed(10);
   	randomize(cCritter::MF_VELOCITY );//| cSprite::MF_RADIUS);
	setBounciness(-.5); //so they hit solid ground and stay there
	setMinTwitchThresholdSpeed(4.0); //Means sprite doesn't switch direction unless it's moving fast
	Real playerx = 0.0;
	if (pownergame)
		playerx = pownergame->pplayer()->position().x();
	randomizePosition(cRealBox(_movebox.locorner(),	// anywhere on screen
		_movebox.hicorner()));
}


void cCritterAddAmmo::update(CPopView *pactiveview, Real dt)
{
	cCritter::update(pactiveview, dt); //Always call this first
	if (_outcode & BOX_LOX) /* use bitwise AND to check if a flag is set. */
		delete_me(); //tell the game to remove yourself
}

BOOL cCritterAddAmmo::collide(cCritter *pcritter)
{
	BOOL collided = cCritter::collide(pcritter);
	if (collided)
	{
		if( (pcritter->IsKindOf(RUNTIME_CLASS(cCritterBallworldPlayer))))	
		{
			((cCritterBallworldPlayer*) pcritter)->ammo = ((cCritterBallworldPlayer*) pcritter)->ammo + 15;
			die();
			return TRUE;
		}
		else if (pcritter->IsKindOf(RUNTIME_CLASS(cCritterWall)))
			return TRUE;
	}
	return FALSE;
}
//==================cCritterTreasure=============

cCritterTreasure::cCritterTreasure(cGame *pownergame):
cCritter(pownergame)
{
	cPolygon *ppoly = new cPolygon(24);
	ppoly->setFilled(FALSE);
	ppoly->setLineWidthWeight(0.1);
	_collidepriority = cCollider::CP_PLAYER + 1; /* Let this guy call collide on the
		player, as his method is overloaded in a special way. */
	setSprite(ppoly);
	setRadius(cGameBallworld::TREASURERADIUS);
	setFixedflag(TRUE);
	moveTo(cVector(_movebox.hix()- 1.5 * cGameBallworld::TREASURERADIUS,
		_movebox.midy()));
}

int cCritterTreasure::collidesWith(cCritter *pothercritter)
{
	if (pothercritter->IsKindOf(RUNTIME_CLASS(cCritterBallworldPlayer)))
		return cCollider::COLLIDEASCALLER;
	else
		return cCollider::DONTCOLLIDE;
}

BOOL cCritterTreasure::collide(cCritter *pcritter)
{
	BOOL collided = cCritter::collide(pcritter);

	if (collided) // touching pcritter
	{
		playSound("Tada");
		pcritter->addScore(100);
		pcritter->addHealth(1);
		pgame()->seedCritters();	
		((cGameBallworld*)pgame())->nextLevel();
		pcritter->moveTo(pgame()->border().locorner());
		return TRUE;
	}
	else
		return FALSE;
}
