 // gamespacewar.cpp: implementation of the cGameSpacewar class.
  //
  //////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "gamespacewar.h"
#include "critterarmed.h"
#include "listener.h"
#include "spriteicon.h"
#include "pop.h" //for playSound
#include "popview.h"
#include "force.h"
#include "spritepolygon.h"
#include "graphicsmfc.h"
#include "graphicsopengl.h"
#include "critterviewer.h"

//#define DEBUGSPACEWAR
	/* If I comment in DEBUGSPACEWAR, I start out with only a few other critters, and with
		my player shield on. */
//#define TESTINGALLCONTROLS
	/* If I comment in TESTINGALLCONTROLS, I allow shapes buttons and all the cursor tools
		 than I really need. */  
//#define SHOWBULLETCOUNT
	/*If you're interested in seeing the bullet count in the status bar
		comment in SHOWBULLETCOUNT.*/
 IMPLEMENT_SERIAL(cGameSpacewar, cGame, 0);
 IMPLEMENT_SERIAL(cCritterAsteroid, cCritter, 0);
 IMPLEMENT_SERIAL(cCritterUFO, cCritterArmedRobot, 0);
 IMPLEMENT_SERIAL(cCritterUFOSmart, cCritterUFO, 0);
 IMPLEMENT_SERIAL(cCritterArmedPlayerSpacewar, cCritterArmedPlayer, 0);
 
  //==============================cGameSpacewar
 	//Size statics.--------------------------------------------------
 Real cGameSpacewar::WORLDSIZE = 20.0; //Edge size of the square world
 Real cGameSpacewar::CRITTERMINRADIUS = 0.75; //Keep them at a fixed size.
 Real cGameSpacewar::CRITTERMAXRADIUS = 0.75; 
 Real cGameSpacewar::BULLETRADIUS = 0.2;
 	//Speed statics.---------------------------------------------------
 Real cGameSpacewar::CRITTERMAXSPEED = 5.0;
 Real cGameSpacewar::SPEEDMULTIPLIER = 1.1; 
 Real cGameSpacewar::CRITTERMAXSPEEDLIMIT = 9.0;
 
Real cCritterAsteroid::CHASEACCELERATION = 0.2; /* 1.0 How strongly to head towards the player.
	If you make this too strong, it overrides the darting away from the bullet. */
Real cCritterAsteroid::DARTACCELERATION = 4.0; //How strongly to accelerate away from bullets. was 10.0
Real cCritterAsteroid::DARTSPEEDUP = 1.25; // How much to speed up when fleeing bullets. was 2.0
 
Real cCritterUFO::CHASEACCELERATION = 1.0; //How strongly to head towards the player. Was 2.0
Real cCritterUFO::DARTACCELERATIONCRITTER = 1.0; //How strongly to accelerate away from other critters.
Real cCritterUFO::DARTSPEEDUPCRITTER = 1.0; /* How much to speed up when fleeing critters. I make
 	this one since I already have a speedup factor from bullets. */
Real cCritterUFO::DARTACCELERATIONBULLET = 10.0; //How strongly to accelerate away from bullets. Was 20.0
Real cCritterUFO::DARTSPEEDUPBULLET = 2.0; // How much to speed up when fleeing bullets. Was 4.0
 	//Score statics ---------------------------------------------
 int cGameSpacewar::ENEMYBULLETVALUE = 4;
 int cGameSpacewar::ENEMYMISSILEVALUE = 8;
 int cCritterAsteroid::VALUE = 4;
 int cCritterUFO::VALUE = 6;
 int cGameSpacewar::SCORELEVEL = 100;
 int cGameSpacewar::UFOATTACKSCORE = 40; /*  Points needed to provoke a UFO attack, fun if this
 		is low and they come fast and furious. */
 	// Health statics ----------------------------------------------
 int cGameSpacewar::LEVELREWARD = 1; //How much extra health the player gets when rewarded.
 int cCritterUFO::HEALTH = 2; 
 int cCritterUFOSmart::HEALTH = 1; 
 int cCritterAsteroid::HEALTH = 2; 
 int cGameSpacewar::PLAYERHEALTH = 10;
 	//Other statics --------------------------------------------------
 Real cCritterUFO::WAITSHOOT = 1.5; //0.1 for steady fire

 Real cGameSpacewar::SMARTUFOPROBABILITY = 0.5;
		/* Probability of a UFO using smart missiles
  		instead of bullets.  Should be between 0.0 and 1.0. */ 
 int cCritterAsteroid::OVERPOPULATIONCOUNT = 10; /* Don't make more than this many asteroids. */
 
  cGameSpacewar::cGameSpacewar():
	_lastinvasionscore(0)  {	
//Fix the menu selections you'll allow.
	_menuflags |= cGame::MENU_AUTOPLAY;
		 //Default _menuflags from cGame has hopper off autoplay off.  Turn on autoplay.
//_menuflags &= ~cGame::MENU_SHAPE;  // Turn off MENU_SHAPE
#ifdef TESTINGALLCONTROLS
	_menuflags |= cGame::MENU_SHAPE;
#endif //TESTINGALLCONTROLS
//Fix your statics
 	cCritter::MINRADIUS = cGameSpacewar::CRITTERMINRADIUS;
  	cCritter::MAXRADIUS = cGameSpacewar::CRITTERMAXRADIUS;
  	cCritter::MAXSPEED = cGameSpacewar::CRITTERMAXSPEED;
  	cCritterBullet::BULLETRADIUS = cGameSpacewar::BULLETRADIUS;
 	cCritterBulletSilver::SCOREVALUE = cGameSpacewar::ENEMYBULLETVALUE;
  	cCritterBulletSilverMissile::SCOREVALUE = cGameSpacewar::ENEMYMISSILEVALUE;
	cCritterBullet::BULLETSPEED *= 1.1;
//Set the cursor tools.
  	_arrayHCURSOR.Add(((CPopApp*)::AfxGetApp())->_hCursorPlay); /*_arrayHCURSOR has
  		only the _hCursorArrow inherited from cGame.  Add a Shooting cursor. */
#ifdef TESTINGALLCONTROLS  //I don't normally want these, but if I want to try out all controls, put 'em in.
	_arrayHCURSOR.Add(((CPopApp*)::AfxGetApp())->_hCursorZap);
  	_arrayHCURSOR.Add(((CPopApp*)::AfxGetApp())->_hCursorReplicate);
  	_arrayHCURSOR.Add(((CPopApp*)::AfxGetApp())->_hCursorDragger);
  	_arrayHCURSOR.Add(((CPopApp*)::AfxGetApp())->_hCursorPin);
#endif //TESTINGALLCONTROLS
//Set your world shape with the _border and wrapflag

   		 //Use a square world.  Comment out for rectangle.
   	_border.set(cGameSpacewar::WORLDSIZE, cGameSpacewar::WORLDSIZE);
 	_border.pcolorstyle()->setFillColor(cColorStyle::CN_BLACK);
  	_border.pcolorstyle()->setLineColor(cColorStyle::CN_YELLOW);
 //Set the backgroudn bitmap
	setBackgroundBitmap(IDB_BACKGROUND);
		 /*  Sets the cSpriteBackground for the 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::WRAP);
  		//cCritter::BOUNCE turns off wrap to make it easier to shoot things.
//Make a player
   	setPlayer(new cCritterArmedPlayerSpacewar(this));
#ifdef DEBUGSPACEWAR
	_seedcount = 0;
#endif //DEBUGSPACWAR
	//Set the default spritetype for asteroids
	_spritetype = cGame::ST_ASTEROIDPOLYGONS; //Used by cCritterAsteroid
  }
    
void cGameSpacewar::seedCritters()
  {
  /* Get rid of any asteroids and bullets, but if there are UFOs, leave them alone. */
  	_pbiota->purgeCritters(RUNTIME_CLASS(cCritterBullet));
  	_pbiota->purgeCritters(RUNTIME_CLASS(cCritterAsteroid));
  	for (int i=0; i < _seedcount; i++)  
  		new cCritterAsteroid(this); 
#ifdef DEBUGSPACEWAR
  	new cCritterUFOSmart(this); 
#endif // DEBUGSPACEWAR
  }

void cGameSpacewar::setAutoplay(int autoplay)
{
	cGame::setAutoplay(autoplay);
	if (autoplay)
		pplayer()->setListener(new cListenerSpaceship());
}

void cGameSpacewar::reset()
{
  	_pbiota->purgeCritters(RUNTIME_CLASS(cCritterUFO));
		 //In case there's any UFOs left
	cGame::reset(); //Calls pplayer()->reset() and seedCritters().
 	cCritter::MAXSPEED = cGameSpacewar::CRITTERMAXSPEED;
	_lastinvasionscore = 0;
}

void cGameSpacewar::initializeView(CPopView *pview)
{
  	cGame::initializeView(pview); //Always call the baseclass method.
//	pview->setGraphicsClass(RUNTIME_CLASS(cGraphicsOpenGL));  //Start in 3D
//	_plightingmodel->setEnableLighting(FALSE); //Try no light in 3D: ugly, slow.
	pview->setCursor(((CPopApp*)::AfxGetApp())->_hCursorPlay);
}

void cGameSpacewar::initializeViewpoint(cCritterViewer *pviewer)
{
	//Same code as base class, but put it here for tweaking.
		/* The two args to setViewpoint are (directiontoviewer, lookatpoint).
			Note that directiontoviewer points FROM the origin TOWARDS the viewer. */
	if (pviewer->is3D())
		pviewer->setViewpoint(cVector3(0.0, -1.0, 2.0), _border.center());
			//Direction to viewer is down a bit, and back off less than that.
	else //2D case.
		pviewer->setViewpoint(cVector::ZAXIS, _border.center());
}

CString cGameSpacewar::statusMessage()
  {
  	CString cStrStatusBar = cGame::statusMessage();
#ifdef SHOWBULLETCOUNT 
 	CString cStrBulletCount;
  	cStrBulletCount.Format("Bullets: %d.", _pbiota->count(RUNTIME_CLASS(cCritterBullet)));
  	cStrStatusBar = cStrStatusBar + "  " + cStrBulletCount;
#endif //SHOWBULLETCOUNT
  	return cStrStatusBar;
  }
  
void cGameSpacewar::adjustGameParameters()
{
#ifndef DEBUGSPACEWAR //Turn this method off when debugging, so you can have no asteroids.
		// (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) Reseed the screen if all the asteroids are gone (There may still be UFOs present)
	if (_pbiota->count(RUNTIME_CLASS(cCritterAsteroid)) == 0 ) 
	{ //If the crittercount - bulletcount = 1, then player's alone, and the enemies are all gone.
		cCritter::MAXSPEED *= SPEEDMULTIPLIER;//Speed the critters  up.
		if (cCritter::MAXSPEED > cGameSpacewar::CRITTERMAXSPEEDLIMIT)
			cCritter::MAXSPEED = cGameSpacewar::CRITTERMAXSPEEDLIMIT;
		seedCritters();
	}
		// (3) Attack with a UFO if enough score has elapsed and there aren't any other UFOs
	if (score() - _lastinvasionscore > cGameSpacewar::UFOATTACKSCORE &&							//More score brings more UFOs
		!_pbiota->count(RUNTIME_CLASS(cCritterUFO)))
					//Don't start a new UFO till old ones are gone.
	{
		playSound("TaDa"); //Warning single.
		if (cRandomizer::pinstance()->randomBOOL(cGameSpacewar::SMARTUFOPROBABILITY))
			 new cCritterUFOSmart(this); 
		else
			 new cCritterUFO(this);
		_lastinvasionscore = score();
	}
#endif // DEBUGSPACEWAR
}
 
 void cGameSpacewar::Serialize(CArchive& ar)
 {
 	cGame::Serialize(ar);
  	if (ar.IsStoring()) // Save 
 		ar << _lastinvasionscore;
  	else //Load 
 		ar >> _lastinvasionscore;
}

//====================cCritterArmedPlayerSpacewar
cCritterArmedPlayerSpacewar::cCritterArmedPlayerSpacewar(cGame *pownergame):
cCritterArmedPlayer(pownergame)
{
  	setSensitive(TRUE);
  	setHealth(cGameSpacewar::PLAYERHEALTH);
  	setNewlevelreward(cGameSpacewar::LEVELREWARD);
  	setNewlevelscorestep(cGameSpacewar::SCORELEVEL);
  	setListener(new cListenerSpaceship());
  	psprite()->setLineColor(cColorStyle::CN_WHITE); //White edges as we use black background.
	setMaxspeed(2.0*cGameSpacewar::CRITTERMAXSPEED);
#ifdef DEBUGSPACEWAR
	setShield(TRUE); //invulnerable for debugging
#endif //DEBUGSPACEWAR
}

void cCritterArmedPlayerSpacewar::reset()
{
	cCritterArmedPlayer::reset();
  	setHealth(cGameSpacewar::PLAYERHEALTH);
}

 //==================== cCritterAsteroid ====================
 
 cCritterAsteroid::cCritterAsteroid(cGame *pownergame):cCritter(pownergame)
 {
  	setHealth(cCritterAsteroid::HEALTH); 
  		/* One more than the number of times it splits.  Splitting is also capped by the
  			cCritterAsteroid::OVERPOPULATIONCOUNT, which might be about 10; if there are
  			more than this many asteroids they don't split when you shoot them. */
 	setValue(cCritterAsteroid::VALUE);
	if (pownergame) //Just to be safe.
		setSprite(pgame()->randomSprite(pownergame->spritetype())); /* Let the game
			set the current default sprite. */
  	randomize(cCritter::MF_VELOCITY | cSprite::MF_RADIUS);
  	psprite()->setLineColor(cColorStyle::CN_WHITE); //White edges as we use black background.
 	addForce(new cForceClassEvade(cCritterAsteroid::DARTACCELERATION,
 		cCritterAsteroid::DARTSPEEDUP, RUNTIME_CLASS(cCritterBullet), FALSE)); 
 			/*	cForceClassEvade is an allpurpose evading force used for evading any class I like.
 				The third argument says which class to evade, and the fourth argument says whether 
 				to evade objects that are of classes that are child classes of the class to evade. 
 				Putting FALSE means I don't evade cCritterBulletSilver and 
 				CritterBulletSilverMissile (as that would be a waste of energy since these 
 				can't hurt me.) 
 			*/
   	moveToMoveboxEdge();
	if (pownergame) //Then we know we added this to a game so pplayer() is valid
 		addForce(new cForceObjectSeek(pplayer(), cCritterAsteroid::CHASEACCELERATION));
 			/* Force for accerlating towards a critter. */
 }
 
 int cCritterAsteroid::damage(int hitstrength)
 {
 	if ( _shieldflag || recentlyDamaged())
 		return 0; /* Bail out right here if you were just damaged or if you were just replicated
 			from a just-damaged guy. Otherwise a newly replicated critter hit by one of a fusillade
 			of bullets that split its parent will get to split as well. */
 	int deathreward = cCritter::damage(hitstrength); /* This is _value (typically nonzero) you get
 		for killing off the critter. */
 	playSound("Ding"); //Signal the hit.
 	if (_health) // If not dead yet, let's replicate unless there's too many cCritterAsteroid.
 	{
 		setRadius(radius()/sqrt(2.0));
 		mutate(cCritter::MF_NUDGE); //Crude way to show a hit.
 		if (pownerbiota()->count(RUNTIME_CLASS(cCritterAsteroid)) < 
 				cCritterAsteroid::OVERPOPULATIONCOUNT)
 			replicate(); //Ask cBiota to make a copy in next updateServiceRequests call.
 	}
 	return deathreward;
 }

 //==================== cCritterUFO ====================
 
 cCritterUFO::cCritterUFO(cGame *pownergame):cCritterArmedRobot(pownergame)
 {
 	setBulletClass(RUNTIME_CLASS(cCritterBulletSilver));
 	setWaitShoot(cRandomizer::pinstance()->randomReal(0.8, 1.2)
 		* cCritterUFO::WAITSHOOT); /* Make this slightly indeterminate so they don't all shoot in 
 			synch in case you make several. */
 	setHealth(cCritterUFO::HEALTH); 
  	setValue(cCritterUFO::VALUE);
	if (pgame())
		setSprite(pgame()->randomSprite(cGame::ST_POLYPOLYGONS));
  	psprite()->setLineColor(cColorStyle::CN_WHITE); /* White edges as we use black background.
 		Note that this color affect bullets you shoot as well as your own sprite. */
 	randomize(cCritter::MF_VELOCITY | cSprite::MF_RADIUS);
 		/* In order to make the UFO motions more chaotic and interesting, I'm going to give them 
 			three competing forces. */
 	addForce(new cForceClassEvade(cCritterUFO::DARTACCELERATIONCRITTER,
 		cCritterUFO::DARTSPEEDUPCRITTER, RUNTIME_CLASS(cCritterAsteroid))); /* This says
			to weakly evade asteroids. Default fourth argument is FALSE, meaning only
			respond to the class you specify and not it's children.*/
 	addForce(new cForceEvadeBullet(cCritterUFO::DARTACCELERATIONBULLET, 
 		cCritterUFO::DARTSPEEDUPBULLET));/* Strongly evade bullets. This force is a child of 
			cForceClassEvade with default RUNTIME_CLASS(cCritterBullet) and FALSE in the thrid
			and fourth arg, so doesn't evade children of cCritterBullet. */
	setWrapflag(cCritter::BOUNCE); //Call out here as the add overrides this.
  	moveToMoveboxEdge();
	if (pownergame) //Then we know we added this to a game so pplayer() is valid
	{
		setTarget(pplayer());
		addForce(new cForceObjectSeek(pplayer(), cCritterUFO::CHASEACCELERATION));
			//Move towards the player.
 		makeServiceRequest("move_to_front");
	}
 }
 
void cCritterUFO::update(CPopView *pactiveview, Real dt)
{
	cCritterArmedRobot::update(pactiveview, dt); //Always call the base class update.
	setWrapflag(cCritter::BOUNCE); /* If I've split or if game has changed wrap, I 
		may have gotten my wrapflag set to cCritter::WRAP, so I always reset it here
		to be save. */
}

 //==================== cCritterUFOSmart ====================

 cCritterUFOSmart::cCritterUFOSmart(cGame *pownergame):
 cCritterUFO(pownergame)
 {
 	setBulletClass(RUNTIME_CLASS(cCritterBulletSilverMissile));
 	setHealth(cCritterUFOSmart::HEALTH); 
 	setSprite(new cSpriteIcon(IDB_UFO));
	setMaxspeed(2*cCritter::MAXSPEED); //Make these guys extra fast.
 }
 
 int cCritterUFO::damage(int hitstrength)
 {
 	if ( _shieldflag || recentlyDamaged())
 		return 0; /* Bail out right here if you were just damaged or if you were just replicated
 			from a just-damaged guy. Otherwise a newly replicated critter hit by one of a fusillade
 			of bullets that split its parent will get to split as well. */
 	int deathreward = cCritter::damage(hitstrength); /* This is _value (typically nonzero) you get
 		for killing off the critter. */
 	playSound("Pop"); //Signal the hit.
 	if (_health) //If not dead yet, let's replicate.
 	{
 		setRadius(radius()/sqrt(2.0));
 		mutate(cCritter::MF_NUDGE); //Crude way to show a hit.
 		replicate(); //Ask cBiota to make a copy in next updateServiceRequests call.
 	}
 	return deathreward;
 }
