// gamedefender3d.cpp: implementation of the generic cGameDefender3D class.
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "gamedefender3d.h"
#include "critterarmed.h"
#include "listener.h"
#include "spriteicon.h"
#include "pop.h" //for playSound
#include "popview.h"
#include "force.h"
#include "spritebubble.h"
#include "graphicsopengl.h"
#include "graphicsmfc.h"
#include "critterviewer.h"
#include "spritemultiicon.h"

IMPLEMENT_SERIAL(cGameDefender3D, cGame, 0);
IMPLEMENT_SERIAL(cCritterDefender3DPlayer, cCritterArmedPlayer, 0);
IMPLEMENT_SERIAL(cCritterDefender3DRival, cCritterArmedRobot, 0);
IMPLEMENT_SERIAL(cCritterDefender3DProp, cCritter, 0);
IMPLEMENT_SERIAL(cCritterDefender3DPropFrag, cCritter, 0);
IMPLEMENT_SERIAL(cCritterDefender3DPlayerBullet, cCritterBullet, 0);
IMPLEMENT_SERIAL(cCritterDefender3DRivalBullet, cCritterBullet, 0);

//#define DEBUGDEFENDER  //Comment DEBUGDEFENDER in to start with only one critter besides player.
//#define LOOPEXPLOSION
	/*Comment in LOOPEXPLOSION to have a bitmap cSpriteLoop  for a prop that's 
	been shot.  This does slow things down a bit, not because of the loop, but because of
	the bitmap.  Comment out LOOPEXPLOSION to just show a hit by changing the sprite to
	a wireframe. */

#define PROPSEEDPERCENTOFZ 0.2 //Means use the low-z 20% of world box to seed prop critters into.

//==============================cGameDefender3D
int cGameDefender3D::PLAYERHEALTH = 10;
int cGameDefender3D::DEFAULTSEEDCOUNT = 8;
int cGameDefender3D::DEFAULTRIVALCOUNT = 0;
Real cCritterDefender3DProp::EXPLOSIONDISPLAYTIME = 0.75;
Real cCritterDefender3DPropFrag::FIXEDLIFETIME = 5.0;
int cCritterDefender3DProp::FRAGCOUNT = 5;

cGameDefender3D::cGameDefender3D() //The constructor gets called only once, at first start.
{	
	_menuflags &= ~cGame::MENU_2D; 
		//Default _menuflags from cGame has hopper off, autoplay off.  Turn off 2D.
	_menuflags &= ~cGame::MENU_CHANGELISTENER; 
		/* The way player is set up, it's bad to use any listener other than
		the default cListenerArrowAttitude. */
	_menuflags &= ~cGame::MENU_BOUNCEWRAP; 
		/* If we allow wrap it gets screwy. */
	_rivalcount = cGameDefender3D::DEFAULTRIVALCOUNT; 
		//The number of cCritterRival that I plan to add.
	_seedcount = cGameDefender3D::DEFAULTSEEDCOUNT; 
		//The number of cCritterDefender3DProp that I plan to add.
#ifdef DEBUGDEFENDER
	_rivalcount = 0;
	_seedcount = 1;
#endif //DEBUGDEFENDER
	_wrapflag = cCritter::BOUNCE; /* Sets the critters to match when added.
		Default wrap is cCritter::WRAP. */
	_spritetype = cGame::ST_SIMPLEPOLYGONS;
		 //Default sprite for cCritterDefender3DProp critters
//Fix your statics
	cCritter::MAXSPEED = 4.0; /* Speed used for _maxspeed in critter constructors. */
//Fix the border
	_border.set(19, 19.0, 41.0); /* We think of x as left/right, y as up/down and
		 z as the depth into the screen. */
//Set the background 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. */
//Make a player
	setPlayer(new cCritterDefender3DPlayer(this));
//Set the cursor tools.
	/*_arrayHCURSOR has only the _hCursorArrow inherited from cGame. 
		 Add a Shooting cursor. */
	_arrayHCURSOR.Add(((CPopApp*)::AfxGetApp())->_hCursorPlay); 
}

/* seedCritters makes and positions any temporary critters.  This gets
	automatically called when we select a game by the CPopDoc::setGameClass call.  It also gets
	called by cGame::reset() if you restart a game.*/
void cGameDefender3D::seedCritters()
{
	pbiota()->purgeNonPlayerNonWallCritters(); 
		/* Clean out any old non-player non-wall critters. Although we don't have 
			walls yet, you might want to put some in. */
 	for (int i=0; i < _seedcount; i++)
 		new cCritterDefender3DProp(this); 
	for (i=0; i<_rivalcount; i++)
		new cCritterDefender3DRival(this);
}

/* cGameDefender3D::reset does not get called at first startup, is called only when you start a
	 new round or 	level of the game.*/
void cGameDefender3D::reset() 
{
	_rivalcount = cGameDefender3D::DEFAULTRIVALCOUNT;
	_seedcount = cGameDefender3D::DEFAULTSEEDCOUNT;
	cGame::reset(); //Calls pplayer()->reset() and seedCritters().
	//Soemtimes reset other parameters here.
}

/* This gets called at startup and when you begin a new game.  This is the
place to set the background or the kind of cursor you want to use. */
void cGameDefender3D::initializeView(CPopView *pview)
{
	cGame::initializeView(pview); //Always call the baseclass method.
	pview->setCursor(((CPopApp*)::AfxGetApp())->_hCursorPlay);
		 //We do want to be able to use the shoot cursor
	pview->setUseBackgroundBitmap(FALSE); //Default was TRUE
	pview->setUseSolidBackground(FALSE); //Default was TRUE
	pview->setGraphicsClass(RUNTIME_CLASS(cGraphicsOpenGL)); //This is a 3D game
	pview->pviewpointcritter()->setListener(new cListenerViewerRide());
		//Or comment this out for non-riding behavior, also good.
}

void cGameDefender3D::initializeViewpoint(cCritterViewer *pviewer)
{
	if (pviewer->plistener()->GetRuntimeClass()
		== (RUNTIME_CLASS(cListenerViewerRide)))
	{
		pviewer->setViewpoint(cVector3(0.3, 0.0, 1.0), _border.center());
			//Always make some setViewpoint call simply to put in a default zoom.
		pviewer->zoom(0.35); //Wideangle
		cListenerViewerRide *prider = (cListenerViewerRide*)(pviewer->plistener());
		prider->setOffset(-3.0*cVector::XAXIS); /* This offset is in the coordinate
			system of the player, where the negative X axis is the negative of the
			player's tangent direction, which means stand right behind the player. */
	}
	else //Not riding the player.
	{
		pviewer->zoom(1.0);
		/* 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.3, 0.0, 1.0), _border.center());
			pviewer->roll(PI/2.0); //rolls the viewer to right oritentation. 
		}
		else //2D case.
			pviewer->setViewpoint(-cVector::ZAXIS, _border.center());
	}
}

CString cGameDefender3D::statusMessage()
{
	CString cStrStatusBar = cGame::statusMessage(); //Get the standard message
		//Add to it or change it.
	return cStrStatusBar;
}

void cGameDefender3D::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) Perhaps do this to reseed the screen if rivals and props are gone.
	int propcount = pbiota()->count(RUNTIME_CLASS(cCritterDefender3DProp));
	int rivalcount = pbiota()->count(RUNTIME_CLASS(cCritterDefender3DRival));
	if (propcount+rivalcount==0) //Player is alone with bullets and fragments
		seedCritters();
// (3) Also don't let the propcount diminish.
			//(need to recheck propcount in case we just called seedCritters).
	propcount = pbiota()->count(RUNTIME_CLASS(cCritterDefender3DProp));
	int propstoadd = _seedcount - propcount;
 	for (int i=0; i < propstoadd; i++)
		new cCritterDefender3DProp(this);
}

void cGameDefender3D::Serialize(CArchive& ar)
{
	cGame::Serialize(ar);
	if (ar.IsStoring()) // Save any extra variables
		ar << _rivalcount ; //Line of form ar << ...
	else //Load any extra variables 
		ar >> _rivalcount; //Line of form ar >> ...
}

//====================cCritterDefender3DPlayer
cCritterDefender3DPlayer::cCritterDefender3DPlayer(cGame *pownergame):
cCritterArmedPlayer(pownergame)
{
		//Set the listener
	setListener(new cListenerArrowAttitude());
		//Set the bullet
 	setBulletClass(RUNTIME_CLASS(cCritterDefender3DPlayerBullet));
	//setWaitShoot(2.0*cCritterArmed::WAITSHOOT); //Don't shoot as fast as usual.
		//Set other variables, perhaps including sprite.
	setHealth(cGameDefender3D::PLAYERHEALTH);
		/* Maybe add some forces like gravity; the player will ignore them unless
			you use a car or spaceship listener, but the bullets it shoots will use them. */
	setMaxspeed(1.3*cCritterBulletSilverMissile::MAXSPEED); //So you can catch them.
	setAttitudeToMotionLock(FALSE); //don't turn your sprite with the motions.
	setAimToAttitudeLock(FALSE); //Don't turn your attitude with the gun.
	setAttitudeTangent(-cVector::ZAXIS); //point down into the world.
	setAimVector(-cVector::ZAXIS);
	setSprite(new cSpriteCircle());
	setWrapflag(cCritter::CLAMP);
	psprite()->setFilled(FALSE); //So I can see through the center of the disk.
	psprite()->setSpriteAttitude( cMatrix::yRotation(PI/2.0)); //To face the user.
	setRadius(0.3);
		/* The cCritterArmedPlayer constructor call has already set the  player's _movebox
		to match the pownergame border.  Now we limit the movebox size. */
	setMoveBox(cRealBox(  //Make your move box the near layer of the world.
		_movebox.locorner() + (0.7*_movebox.zsize())*cVector::ZAXIS, //lo
		_movebox.hicorner())); //hi
	moveTo(_movebox.center());  //Start at center.
	setWrapflag(cCritter::BOUNCE); //don't wrap.
	setPrismDz(0.1); //This will look better than the default PLAYERPRISMDZ = 0.5.
}

void cCritterDefender3DPlayer::draw(cGraphics *pgraphics, int drawflags)
{
	//Overlaod this so to not have the default drawHighlight call of cCritterArmedPlayer::draw
	cCritterArmed::draw(pgraphics, drawflags);
}

void cCritterDefender3DPlayer::reset()
{
	cCritterArmedPlayer::reset();
		//Perhaps use setHealth to restore the health to a high value.
	setHealth(cGameDefender3D::PLAYERHEALTH);
}

BOOL cCritterDefender3DPlayer::collide(cCritter *pcritter)
{
	BOOL collideflag = cCritter::collide(pcritter);
		/* May sometimes do more stuff here if collideflag is TRUE.   */
	if (collideflag && pcritter->IsKindOf(RUNTIME_CLASS(cCritterDefender3DPropFrag)))
	{
		playSound("Ding");
		setHealth(health() + 1);
		pcritter->die();
	}
	return collideflag;
}

void cCritterDefender3DPlayer::update(CPopView *pactiveview, Real dt)
{
	cCritterArmedPlayer::update(pactiveview, dt);//Always call this first
}

int cCritterDefender3DPlayer::damage(int hitstrength)
{
	int damagereward = cCritter::damage(hitstrength); //Default bevavior.
	//Maybe change the sprite or something as well.
	return damagereward;
}

cCritterBullet* cCritterDefender3DPlayer::shoot()
{
	playSound("Gunshot");
	cCritterBullet *pbullet = cCritterArmed::shoot();
	cCritter* paimprop = pgame()->pbiota()->pickClosestAhead(
		cLine(position(), aimvector()), this, PI/4.0,
		RUNTIME_CLASS(cCritterDefender3DProp), TRUE);
	/* Find prop critter closest to your aiming line but ahead of you ("this").  Only
	consider critters within a PI/4.0 cone aroudn the aiming line, and only
	consider critters of the class cCritter or (TRUE) its subclasses.  */
	cCritter* paimenemy = pgame()->pbiota()->pickClosestAhead(
		cLine(position(), aimvector()), this, PI/4.0,
		RUNTIME_CLASS(cCritterDefender3DRival), TRUE);
	/* Find enemy as well. */
	cCritter* paimtarget; /* Prefer shooting enemy. */
	if (paimenemy)
		paimtarget = paimenemy;
	else
		paimtarget = paimprop;
	if (paimtarget) //No point seeking a nonexistent target.
      	pbullet->addForce(new cForceObjectSeek(paimtarget, 20.0));
	return pbullet;
}

//====================cCritterDefender3DPlayerBullet==================
cCritterDefender3DPlayerBullet::cCritterDefender3DPlayerBullet()
{
		/* The default constructor will do enough, but you 
		can override the default triangle sprite and size here. */
	//setSprite(pgame()->randomSprite(cGame::ST_POLYPOLYGONS)); 
	setRadius(2.0*cCritter::BULLETRADIUS); //Make bigger than usual.
}

//==================== cCritterDefender3DProp ====================

cCritterDefender3DProp::cCritterDefender3DProp(cGame *pownergame):
cCritter(pownergame)
{
	//Set the health and value. 
	_value = 10;
	// Make a sprite and use setSprite to install it.
	setPrismDz(2*cSprite::CRITTERPRISMDZ); //Make them thicker to look cooler.

	if (pgame()) //Just to be safe.
		setSprite(pgame()->randomSprite(pgame()->spritetype())); /* Let the game
			set the current default sprite. */
	psprite()->setEdged(FALSE);
		//Set a particular radius you like.
	setRadius(1.25);
		//Add some forces
	setMaxspeed(3.5); //Default is 3.0
	setAttitudeToMotionLock(FALSE); /* don't turn your sprite with the motions,
		instead let it tumble. */
	randomizeSpin(1.0, 5.0);
		//Use addForce to add forces.
	addForce(new cForceClassEvade( 5.0, //acceleration //
		1.0, //dart speedup
		RUNTIME_CLASS(cCritterBullet),
		TRUE)); /*  TRUE means evade all child classes of cCritterBullet. */
	addForce(new cForceGravity(30.0, cVector::ZAXIS)); 
		/* Default strngth is often 25.0, but we change it. The second arg is the
		_pulldirection to fall in, which is up the Z towards player who sits
		at high z looking down. */
	addForce(new cForceDrag()); //Default 0.5
	//It's best to randomize position and velocity last; after radius, movebox, etc. are set.
	randomizePosition(cRealBox(  //Scatter in loz ~20% of the _movebox (= game's _border box).
		_movebox.locorner(),
		_movebox.hicorner() - (1.0 - PROPSEEDPERCENTOFZ)* _movebox.zsize()*cVector::ZAXIS)); 
	randomizeVelocity(); //default range is MINSPEED to _maxspeed.
}

int cCritterDefender3DProp::damage(int hitstrength)
{
	int damagereward = cCritter::damage(hitstrength); //Default bevavior.
	//Maybe change the sprite or something as well.
	return damagereward;
}

BOOL cCritterDefender3DProp::collide(cCritter *pcritter)
{
	BOOL collideflag = cCritter::collide(pcritter);
		//May sometimes do more stuff here if collideflag is TRUE.
	return collideflag;
}

void cCritterDefender3DProp::update(CPopView *pactiveview, Real dt)
{
	cCritter::update(pactiveview, dt); //Always call this first
	if (_outcode & BOX_HIZ) /* use bitwise AND to check if a flag is set. */
	{
		playSound("Bonk");
		pplayer()->damage(1); //punish the player
		delete_me(); //tell the game to remove yourself
		return;
	}
}

void cCritterDefender3DProp::die()
{
		playSound("Explosion");
//Make some new tumbling fragment critters.
 		for (int i=0; i< cCritterDefender3DProp::FRAGCOUNT; i++)
			new cCritterDefender3DPropFrag(this);
//Change the prop sprite
		_age = 0.0; //Use age for a time of the "dying act"
		setUseFixedLifetime(TRUE);
		setFixedLifetime(cCritterDefender3DProp::EXPLOSIONDISPLAYTIME);
		setShield(TRUE); //so you don't die again.
		setAttitudeToMotionLock(FALSE); /* In case this happened to be
			on, turn it off here so you're free to set the attitude as you like. */
		setSpin(0.0); //Stop tumbling
		setVelocity(cVector::ZEROVECTOR); //Stop moving
		setAcceleration(cVector::ZEROVECTOR);
		clearForcearray(); //Stop falling
#ifdef LOOPEXPLOSION
		Real oldradius = radius(); //Save radius because installing new sprite changes it.
		resetAttitude(); //So you're facing front for the "billboard" effect.
		cSpriteLoop *ploop = new cSpriteLoop();
		ploop->add(IDB_EXPLODE1);
		ploop->add(IDB_EXPLODE2);
		ploop->add(IDB_EXPLODE3);
		setSprite(ploop);
		ploop->setFlipwait(0.05); //How fast to flip the frames
		setRadius(oldradius/2.0); //Smaller explosion looks better.
#else //not LOOPEXPLOSION
		psprite()->setFilled(FALSE);
		psprite()->setEdged(TRUE);
		psprite()->setLineColor(psprite()->fillColor());
#endif //LOOPEXPLOSION
}		

//==================== cCritterDefender3DPropFrag ====================
cCritterDefender3DPropFrag::cCritterDefender3DPropFrag(cCritter *pcritterprop)
{
	if (pcritterprop && pcritterprop->pgame())
		pcritterprop->pgame()->add(this); //Sets moveBox.
	setSprite(new cSpriteCircle()); //pgame()->randomSprite(cGame::ST_BUBBLES)); //SIMPLEPOLYGONS));
	psprite()->setEdged(FALSE);
	setMaxspeed(10.0); //Not too fast or player can't intersect them.
	setBounciness(1.0);
	randomizeVelocity(); //3D velocity
	setAttitudeToMotionLock(FALSE); //don't turn your sprite with the motions.
	randomizeSpin(1.0, 5.0);
	setShield(TRUE); // So you don't die again.
	//using lifetime of a bullet, we can define its own though.
	setUseFixedLifetime(TRUE);
	setFixedLifetime(cCritterDefender3DPropFrag::FIXEDLIFETIME);
	if (pcritterprop)
	{
		psprite()->setFillColor(pcritterprop->psprite()->fillColor());
		setRadius(pcritterprop->radius()/3.0);
		moveTo(pcritterprop->position()); // + tangent() * pcritterprop->radius());
	}
	addForce(new cForceDrag(0.1)); //Default is 0.5
	addForce(new cForceGravity(50.0, 0.05*cVector::ZAXIS-cVector::XAXIS)); //Fall visually "down"
}		

int cCritterDefender3DPropFrag::collidesWith(cCritter *pcritterother)
{
	if (pcritterother == pplayer())
		return cCollider::COLLIDEASARG; //so Player can eat them.
	else //to keep the speed up, don't do other collisions.
		return cCollider::DONTCOLLIDE;
}

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

//==================== cCritterDefender3DRival ====================

cCritterDefender3DRival::cCritterDefender3DRival(cGame *pownergame):
cCritterArmedRobot(pownergame)
{
		//Set the health and value. 
	_value = 20;
		// Make a sprite and use setSprite to install it.
	if (pgame())
		setSprite(pgame()->randomSprite(cGame::ST_BITMAPS)); 
		//Choose randomly among available bitmaps
	//	setSprite(new cSpriteIcon(IDB_FACE)); //Or you could use a specific bitmap.
		//Always set target for cCritterArmedRobot
	setTarget(pplayer()); //Makes it automatically aim at pplayer.
		//Set the bullet
 	setBulletClass(RUNTIME_CLASS(cCritterDefender3DRivalBullet));
		//Set wait time between shots
	setWaitShoot(10.0); //Seconds between shots fired.
	setGunlength(1.0);
		//OR Erase the ugly gun line from these
	//setGunlength(0.0);
		// COMMENT NEXT LINE OUT TO TURN SHOOTING ON.
	//setArmed(FALSE); //TURN OFF THE SHOOTING.
	randomize(cSprite::MF_RADIUS);// Randomize size if you like.
		//Add some forces
	addForce(new cForceClassEvade(20.0, 4.0, 
		RUNTIME_CLASS(cCritterDefender3DPlayerBullet)));
	randomize(cCritter::MF_POSITION);
	randomize(cCritter::MF_VELOCITY); //Randomize velocity
}

int cCritterDefender3DRival::damage(int hitstrength)
{
	int damagereward = cCritter::damage(hitstrength); //Default bevavior.
	//Maybe change the sprite or something as well.
	return damagereward;
}	

BOOL cCritterDefender3DRival::collide(cCritter *pcritter)
{
	BOOL collideflag = cCritter::collide(pcritter);
		//May sometimes do more stuff here if collideflag is TRUE.
	return collideflag;
}

void cCritterDefender3DRival::update(CPopView *pactiveview, Real dt)
{
	cCritterArmedRobot::update(pactiveview, dt);//Always call this first
}

//====================cCritterDefender3DRivalBullet==================
cCritterDefender3DRivalBullet::cCritterDefender3DRivalBullet()
{
		//Maybe use bubbles for fun.
//	setSprite(new cSpriteBubblePie());
//	randomize(cPolygon::MF_COLOR); //randomize color of sprite.
//	_dieatedges = FALSE; //Include this in case you want the bullet to wrap around
}

void cCritterDefender3DRivalBullet::initialize(cCritterArmed *pshooter)
{
	cCritterBullet::initialize(pshooter);
	addForce( new cForceObjectSeek(ptarget(), 20.0)); 
		// Param is chaseacceleration.  Maybe should make this raw number a static.
	setRadius(0.5 * pshooter->radius());
		 //Make bullet size depend on shooter, but don't have to do this.
}

