// heroesproto.cpp: implementation of the cGameHeroesOfCIS game class.
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "heroesproto.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(cGameHeroesOfCIS, cGame, 0);
IMPLEMENT_SERIAL(cCritterHeroesPlayer, cCritterPlayer, 0);
IMPLEMENT_SERIAL(cCritterPrintQueue, cCritter, 0);
IMPLEMENT_SERIAL(cCritterSupplyCabinet, cCritter, 0);
IMPLEMENT_SERIAL(cCritterPrinter, cCritter, 0);
//IMPLEMENT_SERIAL(cCritterStubRival, cCritterArmedRobot, 0);
//IMPLEMENT_SERIAL(cCritterStubProp, cCritter, 0);
//IMPLEMENT_SERIAL(cCritterHeroesPlayerBullet, cCritterBullet, 0);
//IMPLEMENT_SERIAL(cCritterStubRivalBullet, cCritterBullet, 0);


//#define DEBUGSTUB  //Comment DEBUGSTUB in to start with only one critter besides player.
//==============================cGameHeroesOfCIS
//int cGameHeroesOfCIS::PLAYERHEALTH = 3;  //Start with a very high health for demo purposes,
		//Do you don't die so fast.  Usually use a lower value for a real game.
int cGameHeroesOfCIS::DEFAULTSEEDCOUNT = 1;
int cGameHeroesOfCIS::DEFAULTRIVALCOUNT = 0;

int cGameHeroesOfCIS::MAXQUEUE = 10;

/* Game world parameters. */

Real cGameHeroesOfCIS::BLANK_EDGE_WEIGHT = 0.03; 
Real cGameHeroesOfCIS::PLAYFIELD_WEIGHT = 0.7; 
Real cGameHeroesOfCIS::PLAYFIELD_STATUSBOX_SPACER_WEIGHT = 0.03; 
	//Now some statics to define the graphical appearance of the boxes.
Real cGameHeroesOfCIS::BOX_LINEWIDTH_WEIGHT = 0.01; // thickness of the edge lines as a percent of window size.
COLORREF cGameHeroesOfCIS::PLAYFIELD_FILLCOLOR = cColorStyle::CN_LIGHTBLUE;
COLORREF cGameHeroesOfCIS::PLAYFIELD_LINECOLOR = cColorStyle::CN_BLACK;
COLORREF cGameHeroesOfCIS::STATUSBOX_FILLCOLOR = cColorStyle::CN_LIGHTGREEN;
COLORREF cGameHeroesOfCIS::STATUSBOX_LINECOLOR = cColorStyle::CN_BLACK;

/* Player Class Constants */
Real cCritterHeroesPlayer::MAX_SPEED = 15.0;



cGameHeroesOfCIS::cGameHeroesOfCIS() //The constructor gets called only once, at first start.
{	
	_rivalcount = cGameHeroesOfCIS::DEFAULTRIVALCOUNT; //The number of cCritterRival that I plan to add.
	_seedcount = cGameHeroesOfCIS::DEFAULTSEEDCOUNT; //The number of cCritterStubProp that I plan to add.
	_wrapflag = cCritter::BOUNCE;

	_spritetype = cGame::ST_BUBBLES; //Default sprite for cCritterStubProp critters
	_menuflags = 0;

	//Fix your statics
	cCritter::MAXSPEED = 4.0; /* Customary to set critter speed, usually the
		bigger the world the higher the speed you want. */

	/**********************
	 * Create game world. *
	 **********************/

    //Set your world shape _border.set(width, height)
	_border.set(28.8, 19.2);//Standard is (14.4, 9.6), can override here

	//setBackgroundBitmap(IDB_STREET);	
		/*  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. */

	/*
	 * We use framebox to set our game in a bit from the window border all
	 * the way around.  And then we let the _playfield to be the left part of
	 * the framebox, skip a little space, and put _statusbox in the rest of
	 * the framebox.  We need to prepare _playfield and _statusbox before calling seedBubbles,
	 * as that call will use them.
	 */
	cRealBox framebox = _border.innerBox(cGameHeroesOfCIS::BLANK_EDGE_WEIGHT *
		_border.minsize());
	_playfield = cRealBox(framebox.locorner(),
		cVector(framebox.lox()+PLAYFIELD_WEIGHT*framebox.xsize(), framebox.hiy()));
	_playfield.pcolorstyle()->setFillColor(cGameHeroesOfCIS::PLAYFIELD_FILLCOLOR);
	_playfield.pcolorstyle()->setLineWidthWeight(cGameHeroesOfCIS::BOX_LINEWIDTH_WEIGHT);
	_playfield.pcolorstyle()->setLineColor(cGameHeroesOfCIS::PLAYFIELD_LINECOLOR);
	_statusbox = cRealBox(cVector(_playfield.hix() +
		cGameHeroesOfCIS::PLAYFIELD_STATUSBOX_SPACER_WEIGHT*_border.minsize(), framebox.loy()),
		framebox.hicorner());
	_statusbox.pcolorstyle()->setFillColor(cGameHeroesOfCIS::STATUSBOX_FILLCOLOR);
	_statusbox.pcolorstyle()->setLineWidthWeight(cGameHeroesOfCIS::BOX_LINEWIDTH_WEIGHT);
	_statusbox.pcolorstyle()->setLineColor(cGameHeroesOfCIS::STATUSBOX_LINECOLOR);

//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. */
//Set wrap with setWrapflag(cCritter::WRAP) or cCritter::BOUNCE.


	_arrayHCURSOR.SetSize(0); // turn off options menu

//Make a player
	setPlayer(new cCritterHeroesPlayer(this)); //"this" arg sets default player _movebox to match _border.
//Possibly make and add any permanent critters here.

	_level = 2;
}

/*
 * printerExploded checks to see if any printers have printqueues greater than MAXQUEUE
 */
BOOL cGameHeroesOfCIS::printerExploded()
{
	for (int i = 0; i < _printercount; ++i) {
		if (_pprinter[i]->pprintqueue()->queue() > MAXQUEUE)
			return TRUE;
	}

	return FALSE;
}

/* 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 cGameHeroesOfCIS::seedCritters()
{
	/*
	 * Clean out any old non-player non-wall critters.
	 */
	pbiota()->purgeNonPlayerNonWallCritters(); 

	/*
	 * Create and place printers and supply cabinet.
	 */
	switch (_level) {
	case 0:
		// A supply cabinet in the upper right
		_psupplycabinet[0] = new cCritterSupplyCabinet(this);
		_psupplycabinet[0]->moveTo(cVector(
			playfield().hix() - _psupplycabinet[0]->radius() - 2 * pplayer()->radius(),
			playfield().hiy() - _psupplycabinet[0]->radius() - 2 * pplayer()->radius()));

		_printercount = 1;

		// A printer in the lower left
		_pprinter[0] = new cCritterPrinter(this);
		_pprinter[0]->moveTo(cVector(
			playfield().lox() + _pprinter[0]->radius() + 2 * pplayer()->radius(),
			playfield().loy() + _pprinter[0]->radius() + 2 * pplayer()->radius()));
		_pprinter[0]->pprintqueue()->moveTo(cVector(
			statusbox().lox() + statusbox().xsize()/2,
			statusbox().loy() + statusbox().ysize()/2));
		break;

	case 1:
		// A supply cabinet in the upper right
		_psupplycabinet[0] = new cCritterSupplyCabinet(this);
		_psupplycabinet[0]->moveTo(cVector(
			playfield().hix() - _psupplycabinet[0]->radius() - 2 * pplayer()->radius(),
			playfield().hiy() - _psupplycabinet[0]->radius() - 2 * pplayer()->radius()));

		_printercount = 2;

		// A printer in the lower left
		_pprinter[0] = new cCritterPrinter(this);
		_pprinter[0]->moveTo(cVector(
			playfield().lox() + _pprinter[0]->radius() + 2 * pplayer()->radius(),
			playfield().loy() + _pprinter[0]->radius() + 2 * pplayer()->radius()));
		_pprinter[0]->pprintqueue()->moveTo(cVector(
			statusbox().lox() + statusbox().xsize()/2,
			statusbox().loy() + statusbox().ysize()/2));

		// A printer in the upper left
		_pprinter[1] = new cCritterPrinter(this);
		_pprinter[1]->moveTo(cVector(
			playfield().lox() + _pprinter[1]->radius() + 2 * pplayer()->radius(),
			playfield().hiy() - _pprinter[1]->radius() - 2 * pplayer()->radius()));
		_pprinter[1]->pprintqueue()->moveTo(cVector(
			statusbox().lox() + statusbox().xsize()/2,
			statusbox().loy() + statusbox().ysize()/2 + 3 * _pprinter[1]->pprintqueue()->radius()));
		break;

	case 2:
		// A supply cabinet in the upper right
		_psupplycabinet[0] = new cCritterSupplyCabinet(this);
		_psupplycabinet[0]->moveTo(cVector(
			playfield().hix() - _psupplycabinet[0]->radius() - 2 * pplayer()->radius(),
			playfield().hiy() - _psupplycabinet[0]->radius() - 2 * pplayer()->radius()));

		_printercount = 3;

		// A printer in the lower left
		_pprinter[0] = new cCritterPrinter(this);
		_pprinter[0]->moveTo(cVector(
			playfield().lox() + _pprinter[0]->radius() + 2 * pplayer()->radius(),
			playfield().loy() + _pprinter[0]->radius() + 2 * pplayer()->radius()));
		_pprinter[0]->pprintqueue()->moveTo(cVector(
			statusbox().lox() + statusbox().xsize()/2,
			statusbox().loy() + statusbox().ysize()/2));

		// A printer in the upper left
		_pprinter[1] = new cCritterPrinter(this);
		_pprinter[1]->moveTo(cVector(
			playfield().lox() + _pprinter[1]->radius() + 2 * pplayer()->radius(),
			playfield().hiy() - _pprinter[1]->radius() - 2 * pplayer()->radius()));
		_pprinter[1]->pprintqueue()->moveTo(cVector(
			statusbox().lox() + statusbox().xsize()/2,
			statusbox().loy() + statusbox().ysize()/2 + 3 * _pprinter[1]->pprintqueue()->radius()));

		// A printer in the lower right
		_pprinter[2] = new cCritterPrinter(this);
		_pprinter[2]->moveTo(cVector(
			playfield().hix() - _pprinter[2]->radius() - 2 * pplayer()->radius(),
			playfield().loy() + _pprinter[2]->radius() + 2 * pplayer()->radius()));
		_pprinter[2]->pprintqueue()->moveTo(cVector(
			statusbox().lox() + statusbox().xsize()/2,
			statusbox().loy() + statusbox().ysize()/2 + 6 * _pprinter[2]->pprintqueue()->radius()));
		break;

	default:
		break;
	}

	/* Note that I can have some switch behavior in here depending on _level. */
/*
 	for (int i=0; i < _seedcount; i++)
		 new cCritterStubProp(this); 
	for (i=0; i<_rivalcount; i++)
		new cCritterStubRival(this);
*/
}

/* cGameHeroesOfCIS::reset does not get called at first startup, is called only when you start a
	 new round or level of the game.*/
void cGameHeroesOfCIS::reset() 
{
	_rivalcount = cGameHeroesOfCIS::DEFAULTRIVALCOUNT;
	_seedcount = cGameHeroesOfCIS::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 cGameHeroesOfCIS::initializeView(CPopView *pview)
{
	cGame::initializeView(pview); //Always call the baseclass method.
	pview->setUseBackgroundBitmap(TRUE); //Default is FALSE
//	pview->pviewpointcritter()->setTrackplayer(TRUE); //Always look at the player.
}

void cGameHeroesOfCIS::initializeViewpoint(cCritterViewer *pviewer)
{
		/* 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.
//		pviewer->zoom(4.0);
	}
	else //2D case.
	{
		pviewer->setViewpoint(cVector::ZAXIS, _border.center());
//		pviewer->zoom(2.0);
	}
}

CString cGameHeroesOfCIS::statusMessage()
{
	CString cStrStatusBar;
	int nUpdatesPerSecond;
	CString cStrUpdatespersecond;
	CString cStrWrench;
	CString cStrCount;
	CString cStrScore;
	CString cStrCollisionCount; //Usually leave empty

	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.runningNearMaxSpeed())
			cStrUpdatespersecond += " (Near Max)";
		if (((CPopApp*)::AfxGetApp())->_timer.runningNearMinSpeed())
			cStrUpdatespersecond += " (Slower than Real Time)";
	}
	else
			cStrUpdatespersecond.Format("Animation is paused.");
	cStrScore.Format("Score: %d.", score() );
	cStrWrench.Format("Wrench: %d.", wrench());
//	cStrCollisionCount.Format("Collide-Checks: %d.", pcollider()->GetSize());
	int crittercount = _pbiota->count(RUNTIME_CLASS(cCritter));
#ifndef DONTCOUNTALLCRITTERS
	cStrCount.Format("Total Critters: %d.", crittercount);
#else //DONTCOUNTALLCRITTERS
	int bulletcount = _pbiota->count(RUNTIME_CLASS(cCritterBullet));
	crittercount -= bulletcount;
	if (visibleplayer()) /*Subtract off 1 for player as well. */
		crittercount -= 1;
	cStrCount.Format("Other Critters: %d.", crittercount);
#endif //DONTCOUNTALLCRITTERS
	cStrStatusBar = cStrScore + "  " + cStrWrench + "  " + cStrCount + "  " + cStrUpdatespersecond + 
		"  " + cStrCollisionCount; 
	return cStrStatusBar;
}

void cGameHeroesOfCIS::adjustGameParameters()
{
// (1) End the game if a printer explodes
	if (printerExploded() && !_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 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.
	/* You might for instance have something like this:
	if (_level == 1 && score() > 100)
	{
		_level = 2;
		//Possibly delete some critters added in the constructor.
		seedCritters();
	}
	*/
/*
	int propcrittercount =
		pbiota()->count(RUNTIME_CLASS(cCritterStubProp));
	if  (propcrittercount < _seedcount)
		new cCritterStubProp(this);
*/
}

void cGameHeroesOfCIS::drawBackground(cGraphics *pgraphics, int drawflags)
{
    //Draw the fillings of the boxes first, before critters.
	cRealBox2 playfieldfloor(_playfield);
	cRealBox2 statusboxfloor(_statusbox);

	playfieldfloor.pcolorstyle()->setEdged(FALSE);
	playfieldfloor.pcolorstyle()->setFilled(TRUE);
	playfieldfloor.draw(pgraphics, drawflags);

	statusboxfloor.pcolorstyle()->setEdged(FALSE);
	statusboxfloor.pcolorstyle()->setFilled(TRUE);
	statusboxfloor.draw(pgraphics, drawflags);
}

void cGameHeroesOfCIS::drawForeground(cGraphics *pgraphics, int drawflags)
{
//	cGame::drawForeground(pgraphics, drawflags);
	//Don't need the outer border box so don't call the base class drawForeground.
/* Draw the edges of the two boxes, letting them pop into 3D if pgraphics can show it. */
	_statusbox.pcolorstyle()->setEdged(TRUE);
	_statusbox.pcolorstyle()->setFilled(FALSE);
	_playfield.pcolorstyle()->setEdged(TRUE);
	_playfield.pcolorstyle()->setFilled(FALSE);
	_statusbox.draw(pgraphics, drawflags);
	_playfield.draw(pgraphics, drawflags);

}

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

//---------------- Player status methods------------

int cGameHeroesOfCIS::wrench()
{
	ASSERT(pplayer());
	return ((cCritterHeroesPlayer*)pplayer())->wrench();
}



//====================cCritterHeroesPlayer
cCritterHeroesPlayer::cCritterHeroesPlayer(cGame *pownergame):
cCritterPlayer(pownergame)
{
    _wrench = 0;

	//Set the listener
	setListener(new cListenerArrow());

	setMaxspeed(MAX_SPEED);

	setArmed(FALSE); // Don't allow player to shoot

	/* Constrain player movement to playfield. */
    setMoveBox(((cGameHeroesOfCIS*)pownergame)->playfield());

	/* Force player to point upwards. */
	setAttitudeToMotionLock(FALSE);
	setAttitudeTangent(cVector::YAXIS);
}

void cCritterHeroesPlayer::reset()
{
	cCritterPlayer::reset();

	_wrench = 0;
}

BOOL cCritterHeroesPlayer::collide(cCritter *pcritter)
{
	BOOL collideflag = cCritter::collide(pcritter);
		/* May sometimes do more stuff here if collideflag is TRUE.  In the code
		below, for instance, we have the player "eat" cCritterStubProp critters, that is,
		increasing the cCritterHeroesPlayerown health and killing the cCritterStubProp
		when they touch. If you don't want this behavior take these lines out. */
	
	if (collideflag && pcritter->IsKindOf(RUNTIME_CLASS(cCritterSupplyCabinet)))
	{
		playSound("Ding");
		_wrench = 1;

	}
	if (collideflag && pcritter->IsKindOf(RUNTIME_CLASS(cCritterPrinter)))
	{
		cCritterPrinter *printer = (cCritterPrinter*)pcritter;
		if(printer->isBroken() && _wrench > 0)
		{
			playSound("Tada");
			printer->fixMe();
			_wrench = 0;
			++_score;
		}
	}
	
	return collideflag;
}

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

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

//==================== cCritterPrintQueue ====================

cCritterPrintQueue::cCritterPrintQueue(cGame *pownergame):
cCritter(pownergame)
{
	// Set critter-specific variables
	setSprite(pgame()->randomSprite(cGame::ST_SIMPLEPOLYGONS)); 
	setRadius(0.5);

	// Make print queues immobile and invincible.
	setFixedflag(TRUE);
	setShield(TRUE);

	// Set print-queue-specific variables.
	_queue = 0;
}

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

	switch (queue()) {
	case 0:
		setSprite(new cSpriteIcon(IDB_QUEUE_0)); 
		break;
	case 1:
		setSprite(new cSpriteIcon(IDB_QUEUE_1)); 
		break;
	case 2:
		setSprite(new cSpriteIcon(IDB_QUEUE_2)); 
		break;
	case 3:
		setSprite(new cSpriteIcon(IDB_QUEUE_3)); 
		break;
	case 4:
		setSprite(new cSpriteIcon(IDB_QUEUE_4)); 
		break;
	case 5:
		setSprite(new cSpriteIcon(IDB_QUEUE_5)); 
		break;
	case 6:
		setSprite(new cSpriteIcon(IDB_QUEUE_6)); 
		break;
	case 7:
		setSprite(new cSpriteIcon(IDB_QUEUE_7)); 
		break;
	case 8:
		setSprite(new cSpriteIcon(IDB_QUEUE_8)); 
		break;
	case 9:
		setSprite(new cSpriteIcon(IDB_QUEUE_9)); 
		break;
	case 10:
		setSprite(new cSpriteIcon(IDB_QUEUE_10)); 
		break;
	default:
		break;
	}
}

//==================== cCritterSupplyCabinet ====================

cCritterSupplyCabinet::cCritterSupplyCabinet(cGame *pownergame):
cCritter(pownergame)
{
	// Set critter-specific variables
	setSprite(pgame()->randomSprite(cGame::ST_SIMPLEPOLYGONS)); 
	setRadius(0.5);

	// Make supply cabinets immobile and invincible.
	setFixedflag(TRUE);
	setShield(TRUE);
}

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

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

//==================== cCritterPrinter ====================

cCritterPrinter::cCritterPrinter(cGame *pownergame):
cCritter(pownergame)
{
	// Set critter-specific variables
	setSprite(new cSpriteIcon(IDB_BUM));
	setRadius(0.5);

	//Make printers immobile and invincible.
	setFixedflag(TRUE);
	setShield(TRUE);

	// Set printer-specific variables
	_pprintqueue = new cCritterPrintQueue(pownergame);
	_broken = FALSE;

	// Temp
	breakMe();
}

BOOL cCritterPrinter::isBroken()
{
	return _broken;
}

void cCritterPrinter::breakMe()
{
	if (!isBroken())
	{
		_broken = TRUE;
		setSprite(new cSpriteIcon(IDB_REDSQUARE));
		setRadius(0.5);
		setAge(0.0);
	}
}

void cCritterPrinter::fixMe()
{
	if (isBroken())
	{
		_broken = FALSE;
		setSprite(new cSpriteIcon(IDB_BUM));
		setRadius(0.5);
		setAge(0.0);
	}
}

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

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

	if (isBroken() && age() >= 1.0) {
		setAge(0.0);
		pprintqueue()->incQueue(1);
	}
	if (!isBroken() && age() >= 3.0) {
		setAge(0.0);
		pprintqueue()->decQueue(1);

		if (cRandomizer::pinstance()->randomReal(0, 1) > 0.75)
			breakMe();
	}
}
