// gameworms.cpp: implementation of the generic cGameWorms class.
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "gameworms.h"
#include "critterarmed.h"
#include "listener.h"
#include "spriteicon.h"
#include "spritemultiicon.h"
#include "pop.h" //for playSound
#include "popview.h"
#include "force.h"
#include "spritebubble.h"

IMPLEMENT_SERIAL(cGameWorms, cGame, 0);
IMPLEMENT_SERIAL(cCritterWormsPlayer, cCritterArmedPlayer, 0);
IMPLEMENT_SERIAL(cCritterWormsRival, cCritterArmedRobot, 0);
IMPLEMENT_SERIAL(cCritterWormSegment, cCritter, 0);
IMPLEMENT_SERIAL(cCritterWormsPlayerBullet, cCritterBullet, 0);
IMPLEMENT_SERIAL(cCritterWormsRivalBullet, cCritterBullet, 0);

//Defines
#define PLAYERSPRITELOOP
	/* We have the option of having the cCritterWormsPlayer either use
	an "animated" cSpriteLoop or use a directionally-sensitive cSpriteDirectional.
	Comment in the #define PLAYERSPRITELOOP in for the cSpriteLoop,
	comment it out for the	cSpriteDirectional. */

//==============================cGameWorms
int cGameWorms::PLAYERHEALTH = 5; 
int cGameWorms::WORMCOUNT = 3;
Real cGameWorms::SPRINGFORCE = 3.0;

cGameWorms::cGameWorms(): //The constructor gets called only once, at first start.
	_rivalcount(3)
{	
	_menuflags &= ~cGame::MENU_SHIELD; //We don't use the shield control in this game.
	_menuflags &= ~cGame::MENU_SHAPE; //We don't want to select alternate sprites.
//Usually we set _seedcount.
	_seedcount = 6; //The number of cCritterWormSegment that I plan to add.
//Fix your statics
	cCritter::MAXSPEED = 4.0; /* Customary to set critter speed, usually the
		bigger the world the higher the speed you want. */
//Set the cursor tools.
	/*_arrayHCURSOR has only the _hCursorArrow inherited from cGame. 
		 Add a Shooting cursor and a Dragging cursor for fun. */
	_arrayHCURSOR.Add(((CPopApp*)::AfxGetApp())->_hCursorPlay); 
	_arrayHCURSOR.Add(((CPopApp*)::AfxGetApp())->_hCursorDragger); 
	_arrayHCURSOR.Add(((CPopApp*)::AfxGetApp())->_hCursorPin);
	_arrayHCURSOR.Add(((CPopApp*)::AfxGetApp())->_hCursorReplicate); 
	_arrayHCURSOR.Add(((CPopApp*)::AfxGetApp())->_hCursorZap); 
//Set your world shape _border.set(width, height)
	_border.set(31.0, 19.0), //Standard is (14.4, 9.6), can override here..
//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.
	setWrapflag(cCritter::BOUNCE);
//Make a player
	setPlayer(new cCritterWormsPlayer());
//Possibly make and add any permanent critters here.
}

void cGameWorms::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. */
	/* Make some new cCritterWormsRival and some new cCritterWormSegment.*/
	for (int i=0; i<_rivalcount; i++)
		new cCritterWormsRival(this);
	cCritterWormSegment *pwormseg;
	cCritterWormSegment *pwormseg_previous; /* This is a special trick for the
		follow-the-leader thing we'll set up in the next loop.  Not standard. */
	for (int k=0; k<cGameWorms::WORMCOUNT; k++)
	{
		pwormseg_previous = NULL;
		for (i=0; i<_seedcount; i++)
		{
			pwormseg = new cCritterWormSegment(this, pwormseg_previous);
			pwormseg_previous = pwormseg;
		}
	}
}

/* cGameWorms::reset does not get called at first startup, is called only when you start a
	 new round or 	level of the game.*/
void cGameWorms::reset() 
{
	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 cGameWorms::initializeView(CPopView *pview)
{
	cGame::initializeView(pview); //Always call baseclass
	pview->setCursor(((CPopApp*)::AfxGetApp())->_hCursorPlay);
			 //Use the shoot cursor
	pview->setUseBackgroundBitmap(TRUE);
			//TRUE if you want the background, FALSE if not.
}

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

void cGameWorms::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 others are gone.
	int othercrittercount = pbiota()->count(RUNTIME_CLASS(cCritter)) -
		pbiota()->count(RUNTIME_CLASS(cCritterBullet)) - 1;
		//Number of critters minus bullets minus player equals other critters.
	if (!othercrittercount) //Player is alone with bullets
		seedCritters();
// (3) Maybe check some other conditions.
	if (!pbiota()->count(RUNTIME_CLASS(cCritterWormsRival)))
		seedCritters();
}

void cGameWorms::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 >> ...
}

//====================cCritterWormsPlayer
cCritterWormsPlayer::cCritterWormsPlayer()
{
		//Set the listener
	setListener(new cListenerScooter());
		//Set the bullet
 	setBulletClass(RUNTIME_CLASS(cCritterWormsPlayerBullet));
	setWaitShoot(2.0*cCritterArmed::WAITSHOOT); //Don't shoot as fast as usual.
		//Set other variables, perhaps including sprite.
	setHealth(cGameWorms::PLAYERHEALTH);
		//Set up a level reward system (see cCritter::addScore for how it works)
  	setNewlevelreward(1);
  	setNewlevelscorestep(100);
		/* Maybe add some forces like gravity; the player will ignore them, but the
			bullets it shoots will use them. */
	setMaxspeed(1.3*cCritterBulletSilverMissile::MAXSPEED); //So you can catch them.

	//Let's test out a cSpriteDirectional or a cSpriteLoop
	//Swith on the 
	
#ifdef PLAYERSPRITELOOP
	cSpriteLoop *pmultisprite = new cSpriteLoop(); //OR Cycle through changes
#else //The not PLAYERSPRITELOOP case we do a directional sprite.
	cSpriteDirectional *pmultisprite = new cSpriteDirectional(); //Change with direction
#endif //End PLAYERSPRITELOOP switch.

	cPolygon *ppolygon = new cPolygon(8);
	ppolygon->setVertex(4, cVector(0, 0)); //Puts a dent in the "back" side.
	ppolygon->setFillColor(cColorStyle::CN_GREEN);
	ppolygon->setDotted(TRUE);
	ppolygon->setDotRadiusWeight(0.3);
	ppolygon->pdotcolorstyle()->setFillColor(cColorStyle::CN_BLUE);
	pmultisprite->add(ppolygon);

	ppolygon = new cPolygon(8);
	ppolygon->setVertex(4, cVector(0, 0)); 
	ppolygon->setFillColor(cColorStyle::CN_YELLOW);
	ppolygon->setDotted(TRUE);
	ppolygon->setDotRadiusWeight(0.3);
	ppolygon->pdotcolorstyle()->setFillColor(cColorStyle::CN_BLUE);
	pmultisprite->add(ppolygon);

	ppolygon = new cPolygon(8);
	ppolygon->setVertex(4, cVector(0, 0)); 
	ppolygon->setFillColor(cColorStyle::CN_RED);
	ppolygon->setDotted(TRUE);
	ppolygon->setDotRadiusWeight(0.3);
	ppolygon->pdotcolorstyle()->setFillColor(cColorStyle::CN_BLUE);
	pmultisprite->add(ppolygon);

	ppolygon = new cPolygon(8);
	ppolygon->setVertex(4, cVector(0, 0)); 
	ppolygon->setFillColor(cColorStyle::CN_PURPLE);
	ppolygon->setDotted(TRUE);
	ppolygon->setDotRadiusWeight(0.3);
	ppolygon->pdotcolorstyle()->setFillColor(cColorStyle::CN_BLUE);
	pmultisprite->add(ppolygon);

	setSprite(pmultisprite);
	setRadius(2.5*cCritter::MINRADIUS);
}

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

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


BOOL cCritterWormsPlayer::collide(cCritter *pcritter)
{
	BOOL collided = cCritter::collide(pcritter);
	if (collided && pcritter->IsKindOf(RUNTIME_CLASS(cCritterWormSegment)))
		damage(1);
	if (collided && pcritter->IsKindOf(RUNTIME_CLASS(cCritterWormsRivalBullet)))
	{
		int radiuslevel = int(10.0*(pcritter->radius())+ 1);
		addScore(radiuslevel*radiuslevel);
		playSound("Ding");
		pcritter->die();
	}
	return collided;
}
//====================cCritterWormsPlayerBullet==================
cCritterWormsPlayerBullet::cCritterWormsPlayerBullet()
{
		//Let's use asteroid-shaped bullets for fun.
	cPolygon *newpoly = new cPolygon();
	newpoly->setRandomAsteroidPolygon(5, 20, cRandomizer::pinstance()->randomReal(0.0, 0.4));
	newpoly->randomize( cPolygon::MF_COLOR);
	setSprite(newpoly); 
	setRadius(2.0*cCritterBullet::BULLETRADIUS); //Make bigger than usual.
}

//==================== cCritterWormsRival ====================
cCritterWormsRival::cCritterWormsRival(cGame *pownergame):
cCritterArmedRobot(pownergame)
{
		//Set the health and value. 
		// Make a sprite and use setSprite to install it.
	_collidepriority = cCollider::CP_CRITTER + 1; /* So it has priority 
		in collisions with worm segments. */
	setSprite(new cSpriteIcon(IDB_FACE)); //For instance
	randomize(cCritter::MF_POSITION);
	setWrapflag(cCritter::BOUNCE);
		//Override the wrapflag setting that was made by adding to pownergame.
		//Always set a target for a cCritterArmedRobot
	if (pownergame)
		setTarget(pownergame->pplayer()); //Makes it automatically aim at pplayer.
		//Set the bullet
 	setBulletClass(RUNTIME_CLASS(cCritterWormsRivalBullet));
		//Set wait time between shots
	setWaitShoot(3.0); //Seconds between shots fired.
		//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 velocity, and size if you like.
	randomize(cCritter::MF_VELOCITY | cSprite::MF_RADIUS);
		//Add some forces
	addForce(new cForceClassEvade(10.0, 2.0, 
		RUNTIME_CLASS(cCritterWormsPlayerBullet)));
}

int cCritterWormsRival::damage(int hitstrength)
{
	setRadius(radius()*1.3);//Let's swell when hit by a bullet.
	if(radius() > 2.0) //Pop when you get too big!
	{
		playSound("Pop");
		return cCritter::damage(hitstrength); //Default bevavior.
	}
	else 
		return 0;
}

BOOL cCritterWormsRival::collide(cCritter *pcritter)
{
	BOOL collided = cCritter::collide(pcritter);
	if (collided && pcritter->IsKindOf(RUNTIME_CLASS(cCritterWormSegment)))
	{
		setRadius(radius()*(0.95));//Let's shrink when hit by a worm.
		if(radius() < 0.3) //Pop when you get too small!
		{
			playSound("Pop");
			die();
		}
	}
	return collided;
}

void cCritterWormsRival::update(CPopView *pactiveview, Real dt)
{
	cCritterArmedRobot::update(pactiveview, dt);//Always call this first
	setAimVector(tangent().defaultNormal()); //Aim at right angles to the player
}

//====================cCritterWormsRivalBullet==================
cCritterWormsRivalBullet::cCritterWormsRivalBullet()
{
		//Let's use bubbles for fun.
	setSprite(new cSpriteBubblePie());
	randomize(cPolygon::MF_COLOR); //randomize color of sprite.
	setRadius(2.0*cCritterBullet::BULLETRADIUS); //Make bigger than usual.
	_dieatedges = FALSE; //So the bullet can wrap around
	_collidepriority = cCollider::CP_CRITTER;
}

void cCritterWormsRivalBullet::initialize(cCritterArmed *pshooter)
{
	cCritterBullet::initialize(pshooter);
	if (pshooter->pgame())
		addForce( //Avoid the player
			new cForceObjectSeek(pshooter->pgame()->pplayer(), 
 				-0.3 * cCritterBulletSilverMissile::CHASEACCELERATION)	);
	setRadius(0.5 * pshooter->radius());
//	setWrapflag(cCritter::WRAP);
}

//==================== cCritterWormSegment ====================

cCritterWormSegment::cCritterWormSegment(cGame *pownergame, cCritter *ppriorsegment):
cCritter(pownergame)
{
		//Set the health and value.  Make a sprite and use setSprite to install it.
	cPolygon *pnewpoly = new cPolygon(7);
	pnewpoly->randomize( cPolygon::MF_COLOR);
	setSprite(pnewpoly);
		//Use addForce to add forces.
		//Randomize velocity if you like.
		//Set a particular radius ifyou like.
	setRadius(0.5);
	if (!ppriorsegment)
		randomize(cCritter::MF_POSITION | cCritter::MF_VELOCITY);
	else
	{
		Real rodlength = radius()+ppriorsegment->radius();
		addForce(new cForceObjectSpringRod(
			ppriorsegment, rodlength, cGameWorms::SPRINGFORCE));
		moveTo(ppriorsegment->position()- rodlength * ppriorsegment->tangent());
		setVelocity(ppriorsegment->velocity());
	}
}

int cCritterWormSegment::damage(int hitstrength)
{
	if (psprite()->IsKindOf(RUNTIME_CLASS(cPolygon)))/* I have to check this
		because if I try and cast a non-cPolygon sprite, the program will crash. */
	{
		cPolygon *ppolygon = (cPolygon*)psprite();
		if (ppolygon->vertCount() == 3) //If you're down to three verts
			return cCritter::damage(hitstrength); //die
		else //Do one less vert
			ppolygon->changeVertexcount(-1);
	}
	return 0; 
}

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

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