<pre>
/*
File: biota.cpp
Uploaded by richprillinger on Tue Nov 27 14:49:25 PST 2001
*/

// Biota.cpp: implementation of the cBiota class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "biota.h"
#include "critter.h"
#include "critterwall.h" //For purgeNonPlayerNonWall
#include "game.h" //for constructing default _pgame. 
#include "spritepolygon.h" //For cPolygon::MF_ALL, used in spawn.
#include "pop.h" //for playSound
#include "force.h" //for cForceObject in removeReferencesTo

#define FREEZEFOCUSCRITTER
	/* Comment this in if you want the focus critter to be frozen, that is, not moved or aged.
We use this so that we can drag objects.
And at one time this seemed like a good feature so that I could fix a critter in place and examine
it, but it seems to be too confusing for users.  A particularly odd side effect when this on
is this apparent bug:  Use Zap cursor on a large asteroid in Spacewar.  Shoot it. 
 When it splits, one child is unkillable and doesn't move.   But this isn't a bug, this
 is only the focus critter behavior.  A focus critter doesn't move and doesn't age, so a
 frozen critter is newborn, and newborn critters are invunerable Click on a blank part of 
the screen to release the frozen critter and make it vulnerable. 
Probably the better thing to do would be, if you ever want to freeze criters in place,
to have a special Freeze Cursor tool. */
//============================= Macros ==========================
IMPLEMENT_SERIAL( cCritterArray, CObArray, 0);
IMPLEMENT_SERIAL( cBiota, cCritterArray, 0);

 //******** CONST STATICS **********************************************
 const int cBiota::NOINDEX = -1;


//==================cCritterArray=====================

cCritterArray::cCritterArray(const cCritterArray &critarray)
{
	SetSize(critarray.GetSize());
	for (int i=0; i< GetSize(); i++)
		ElementAt(i) = critarray.GetAt(i);
}
//==================cBiota=========================
cBiota::cBiota()
{
	_pgame = new cGame(this);
}

cBiota::cBiota(cGame *pgame):
_pgame(pgame)
{}

cBiota::~cBiota()
{
	_free();
} 

void cBiota::_free() /* Delete all members.  This is used in desctructor and before
loading into an existing cBiota object. */
{
	for (int i=0; i<GetSize(); i++)
	{
		delete GetAt(i);
		ElementAt(i) = NULL; // Always get rid of a pointer as soon as it's bad.
	}
	RemoveAll();
}
	
//-------------------- cBiota Serialize methods -----------------------

void cBiota::Serialize(CArchive& ar)
{
	if (ar.IsStoring())
		cCritterArray::Serialize(ar);
	else
	{
		/* We'll get a memory leak unles we make sure the cBiota being read into
			is completely emptied out. Note that the cGame constructor creates a
			cBiota* at startup, so we will indeed be reading into something nonempty. */
		_free(); // The default cBiota will be empty, this is just to be sure.
		cCritterArray::Serialize(ar);
		for (int i=0; i<GetSize(); i++)
			GetAt(i)->setOwner(this);
		for (i=0; i<GetSize(); i++)
			GetAt(i)->fixPointerRefs();
		updateMetric(); //Fix the _metrickey fields and the _metric.
	}
} 

void cBiota::purgeCritters(CRuntimeClass *pruntimeclass)
{ /*Rather than deleting the pointers and removing from the array, we use the
servicerequest mechanism, which handles things like _pgame->pFocus(), etc. */
	for (int i=0; i<GetSize(); i++)
		if (GetAt(i)->IsKindOf(pruntimeclass))
			GetAt(i)->delete_me();
	processServiceRequests();	
}

void cBiota::purgeNonPlayerCritters()
{ //See comment on purgeCritters.  purgeNonPlayerCritters gets rid of all but player.
	for (int i=0; i<GetSize(); i++)
		if (GetAt(i) != _pgame->pplayer())
			GetAt(i)->delete_me();
	processServiceRequests();	
}

void cBiota::purgeNonPlayerNonWallCritters()
{ //See comment on purgeCritters.  purgeNonPlayerCritters gets rid of all but player.
	for (int i=0; i<GetSize(); i++)
		if (GetAt(i) != _pgame->pplayer() && 
			!GetAt(i)->IsKindOf(RUNTIME_CLASS(cCritterWall)))
			GetAt(i)->delete_me();
	processServiceRequests();	
}

void cBiota::removeReferencesTo(cCritter *pdeadcritter)
{
	for (int i=0; i<GetSize(); i++)
	{
		cCritter *pcritter = GetAt(i); //This may already be NULL if you're deleting the cBiota.
		if (pcritter)
		{
			if(pcritter->ptarget() == pdeadcritter)
				pcritter->setTarget(NULL);
			for (int j=0; j<pcritter->pforcearray()->GetSize(); j++)
			{
				if (pcritter->pforcearray()->GetAt(j)->IsKindOf(RUNTIME_CLASS(cForceObject)))
				{
					cForceObject *pforcenodal = (cForceObject *)(pcritter->pforcearray()->GetAt(j));
					if (pforcenodal->pnode() == pdeadcritter)
					{
						delete pforcenodal;
						pcritter->pforcearray()->RemoveAt(j);
					}
				}
			}
		}
	}
}

void cBiota::Add(cCritter* pmember)
{
	if (_index(pmember) != cBiota::NOINDEX) //Don't allow someone to add the same thing twice.
	{
		TRACE("Code warning: Attempt to add the same cCritter* twice to a cBiota object.");
		return;
	}
	cCritterArray::Add(pmember);
	if (pmember)
		pmember->setOwner(this);
}

void cBiota::InsertAt(int index, cCritter* pmember)
{
	cCritterArray::InsertAt(index, pmember);
	if (pmember)
		pmember->setOwner(this);
}

int cBiota::_index(cCritter* pcritter)
{
	for (int i=0; i<GetSize(); i++)
		if (GetAt(i) == pcritter)
			return i;
	return cBiota::NOINDEX; //Means -1.  Use this to mean not found.
}

//-------------  cBiota service request methods ---------

void cBiota::addServiceRequest(cServiceRequest servicerequest)
{
	_servicerequestarray.Add(servicerequest);
}

BOOL cBiota::processServiceRequests()
{
	BOOL success = TRUE;
	int clientindex;
 
	for (int i=0; i<_servicerequestarray.GetSize(); i++)
	{
//We use the add request rather than doing Add out in the code.
		if (_servicerequestarray[i]._request == "add_me")
		{
			Add(_servicerequestarray[i]._pclient);
			continue; //Skip the rest of this loop step
		}
//All the remaining requests depend on the critter already being a member.
		clientindex = _index(_servicerequestarray[i]._pclient);
		if (clientindex == cBiota::NOINDEX)
		{
			success = FALSE;
			continue; //Skip the rest of this block and go to the next i.
		}
		if (_servicerequestarray[i]._request == "delete_me")
			_delete_me(clientindex);		
		else if (_servicerequestarray[i]._request == "move_to_front")
			_move_to_front(clientindex);
		else if (_servicerequestarray[i]._request == "replicate")
			_replicate(clientindex);
		else if (_servicerequestarray[i]._request == "spawn")
			_spawn(clientindex);
		else if (_servicerequestarray[i]._request == "zap")
			_zap(clientindex);
		else //Tell programmer there's no handler for his service request string.
		{
			TRACE(_servicerequestarray[i]._request);
			TRACE(" request not supported by cBiota::processServiceRequests.\n");
		}
	}
	_servicerequestarray.SetSize(0); //You did them all, so empty it out.
	return success; /* FALSE means you couldn't find one of the _pclient 
		requesters.  Either someone put a bad value into the array or one
		of your members posted a request for a "delete_me" or a "convert..." 
		followed by another request, which can't be honored as the caller
		pointer's no longer there. */
}

void cBiota::_delete_me(int clientindex)
{
	if (GetAt(clientindex) == _pgame->pplayer()) //You can't delete the player.
		return;
	BOOL killingfocusflag =  (_pgame->pFocus() == GetAt(clientindex));
	delete GetAt(clientindex);
	RemoveAt(clientindex); //Never leave a bad pointer in the array.
	if (killingfocusflag)
		_pgame->setFocus(NULL);
}

void cBiota::_move_to_front(int clientindex)
{ /* Remove the critter  from the array and move all the others
	one index closer to the front. Then insert the critter at the front.  If the
	_pgame's player is in first place, put it after that, otherwise put it in first
	place.  Idea is to always keep any onscreen player on top. */
	if (GetSize() < 2) /* There's nothing to do if you have one or no members. */
		return;
	cCritter* pmovecritter = GetAt(clientindex);
	RemoveAt(clientindex);
	if (GetAt(0) == _pgame->pplayer()) //We know it's safe to do GetAt(0), as size was >= 2 before removing.
		InsertAt(1, pmovecritter); //After the player
	else
		InsertAt(0, pmovecritter); //On top.
}

void cBiota::_replicate(int clientindex)
{ 
	if (GetAt(clientindex) == _pgame->pplayer()) //You can't replicate the player.
		return;
	cCritter *ptemp = GetAt(clientindex)->clone();
	ptemp->mutate(cCritter::MF_NUDGE); //So it's not on top of me.
	InsertAt(clientindex+1, ptemp);
}

void cBiota::_spawn(int clientindex)
{ 
	if (GetAt(clientindex) == _pgame->pplayer()) //You can't spawn the player.
		return;
	cVector oldposition, oldvelocity;
	CRuntimeClass *pclientclass = GetAt(clientindex)->GetRuntimeClass();
	for( int i=0; i<GetSize(); i++)
	{ /* Don't spawn onto yourself and don't spawn onto a critter of a different class type.
You don't spawn to the player for instance becasue if you and player are both cCritterArmed, then
calling the copy function would change the player's _pbulletclass  */
		if (i==clientindex || GetAt(i)->GetRuntimeClass() != pclientclass)
			continue;
		oldposition = GetAt(i)->position(); //Save position.
		oldvelocity = GetAt(i)->velocity(); //Save velocity.
/* I could clone each critter if I were interested in behavior changes.  But this would cause problems
because I'm not sure the player should be changed, also because there is a connection between
cCritterArmed and cCritterBullet.  If I ever want to copy behavior I might use a limited spawn that
only clones among some copatible critters.
		delete ElementAt(i); //Immediately replace this with a good critter.
		ElementAt(i) = GetAt(clientindex)->clone();
Instead of cloning the critter I will copy the critter params and let this call clone the sprite. */
		ElementAt(i)->copy(GetAt(clientindex)); 
		ElementAt(i)->mutate(cSprite::MF_ALL | cPolygon::MF_ALL);
		ElementAt(i)->moveTo(oldposition);
		ElementAt(i)->setVelocity(oldvelocity);
	}
}

void cBiota::_zap(int clientindex)
{ 
	GetAt(clientindex)->randomize(cCritter::MF_VELOCITY | cSprite::MF_ALL | cPolygon::MF_ALL);
}
//------------------cGame Accessor methods----------
cCritter* cBiota::player(){return _pgame->pplayer();}
cRealBox cBiota::border(){return _pgame->border();}

//---------------- cBiota Pick methods -------------------

//Do pick for points

cCritter* cBiota::pickTopTouched(const cVector &vclick)
{ /* Draw draws the lower-numbered critters last, so those are visually "on top".)*/
	for( int i=0; i<GetSize(); i++)
		if (GetAt(i)->touch(vclick)) 
			return GetAt(i);
	return NULL;
}

cCritter* cBiota::pickClosestTouched(const cVector &vclick)
{
	cCritterArray toucharray = touch(vclick);
	if (toucharray.GetSize() == 0)
		return NULL;
	Real closestdistance = BIG_REAL, testdistance;
	int closesti = 0;
	for( int i=0; i<toucharray.GetSize(); i++)
	{
		testdistance = toucharray[i]->distanceTo(vclick);
		if (testdistance < closestdistance)
		{
			closestdistance = testdistance;
			closesti = i;
		}
	}
	return toucharray[closesti];
}

cCritterArray cBiota::touch(const cVector &vclick)
{
	cCritterArray toucharray;
	for( int i=0; i<GetSize(); i++)
		if (GetAt(i)->touch(vclick))
			toucharray.Add(GetAt(i)); 
	return toucharray;
}	

//And then do it for cCritter center points

cCritterArray cBiota::touch(cCritter* pcritter)
{
	cCritterArray toucharray;
	for( int i=0; i<GetSize(); i++)
		if (pcritter->touch(GetAt(i)))
			toucharray.Add(GetAt(i)); 
	return toucharray;
}	

cCritter* cBiota::closestCritter(cCritter *pcenter, CRuntimeClass *pruntimeclass, BOOL includesubclasses)
{
	cCritter *pclose = NULL;
	cCritter *ptest;
	Real closedistance = BIG_REAL;
	Real testdistance;
	for (int i=0; i<GetSize(); i++)
	{
		ptest = GetAt(i);
		if (ptest == pcenter)
			continue;
		if(!(
			(includesubclasses && ptest->IsKindOf(pruntimeclass)) ||
			(!includesubclasses && ptest->GetRuntimeClass()==pruntimeclass)
		))
			continue;
		testdistance = pcenter->distanceTo(ptest);
		if (testdistance < closedistance)
		{
			testdistance = closedistance;
			pclose = ptest;
		}
	}
	return pclose;
}

//And then do it for cLine

cCritter* cBiota::pickTopTouched(const cLine &sightline, cCritter *pcritterignore)
{ /* Draw draws the lower-numbered critters last, so those are visually "on top".)*/
	for( int i=0; i<GetSize(); i++)
		if (GetAt(i)->touch(sightline) && GetAt(i) != pcritterignore) 
			return GetAt(i);
	return NULL;
}

cCritter* cBiota::pickClosestTouched(const cLine &sightline, cCritter *pcritterignore)
{
	cCritterArray toucharray = touch(sightline, pcritterignore);
	if (toucharray.GetSize() == 0)
		return NULL;
	Real closestdistance = BIG_REAL, testdistance;
	int closesti = 0;
	for( int i=0; i<toucharray.GetSize(); i++)
	{
		testdistance = toucharray[i]->distanceTo(sightline);
		if (testdistance < closestdistance)
		{
			closestdistance = testdistance;
			closesti = i;
		}
	}
	return toucharray[closesti];
}

cCritter* cBiota::pickClosest(const cLine &sightline, cCritter *pcritterignore)
{
	Real closestdistance = BIG_REAL, testdistance;
	int closesti = 0;
	for( int i=0; i<GetSize(); i++)
	{
		if (GetAt(i) == pcritterignore)
			continue; //Skip this critter, go to next iteration of the loop.
		testdistance = GetAt(i)->distanceTo(sightline);
		if (testdistance < closestdistance)
		{
			closestdistance = testdistance;
			closesti = i;
		}
	}
	return GetAt(closesti);
}

cCritterArray cBiota::touch(const cLine &sightline, cCritter *pcritterignore)
{
	cCritterArray toucharray;
	for( int i=0; i<GetSize(); i++)
		if (GetAt(i)->touch(sightline) && GetAt(i) != pcritterignore)
			toucharray.Add(GetAt(i)); 
	return toucharray;
}	



//---------------- cBiota Array-Walking methods -------------------

void cBiota::draw(cGraphics *pgraphics, int drawflags)
{
	cCritter *pcritter;
	for (int i = GetSize()-1; i>=0; i--) /* Draw the lower-numbered critters last, 
		so those are visually "on top".)*/
	{
		pcritter = GetAt(i);
		if (!pcritter)
			return;
		pcritter->draw(pgraphics, drawflags);
		if (pcritter == _pgame->pFocus())
			pcritter->drawHighlight(pgraphics, cSprite::HIGHLIGHTRATIO);
	}
}

void cBiota::setNewgeometryflag(BOOL onoff)
{
	for( int i=0; i<GetSize(); i++)
		GetAt(i)->psprite()->setNewgeometryflag(onoff);
}

int cBiota::count(CRuntimeClass *pruntimeclass, BOOL includesubclasses)
{
	int found = 0;
	for( int i=0; i<GetSize(); i++)
		if(
			(includesubclasses && GetAt(i)->IsKindOf(pruntimeclass)) ||
			(!includesubclasses && GetAt(i)->GetRuntimeClass()==pruntimeclass)
		)
			found++;
	return found;
}

void cBiota::update(Real dt, CPopView *pview)
{
	for( int i=0; i<GetSize(); i++)
		GetAt(i)->update(dt, pview);
}

void cBiota::feellistener(Real dt)
{
	for( int i=0; i<GetSize(); i++)
		GetAt(i)->feellistener(dt);
}

void cBiota::move(Real dt)
{
	for( int i=0; i<GetSize(); i++)
// We don't move a critter it is the focus critter AND it's not the player.
#ifdef FREEZEFOCUSCRITTER
		if (GetAt(i) != pgame()->pFocus() || GetAt(i) == pgame()->pplayer())
#endif //FREEZEFOCUSCRITTER
			GetAt(i)->move(dt);
}

void cBiota::animate(Real dt)
{
	for( int i=0; i<GetSize(); i++)
		GetAt(i)->animate(dt); //Perhaps rotate it or something.
}

/* We'll look at (0,1), (0,2), (0,3), ... then (1,2), (1,3), ... and so on.  We'll let the cGame
decide (a) if you want to even consider colliding a given pair of critters, (b) if the two critters 
are close enough (under whatever standard of "closeness") to collide (c) which critter's collide method
to call if they are close. */
void cBiota::collide()
{
	for( int i=0; i<GetSize(); i++)
		for (int j = i+1; j<GetSize(); j++)
			_pgame->collide(GetAt(i), GetAt(j));
}

void cBiota::setWrapflag(int wrapflag)
{
	for( int i=0; i<GetSize(); i++)
		GetAt(i)->setWrapflag(wrapflag);
}

 //=============Metric methods
void cBiota::updateMetric()
{
#ifdef USEMETRIC
/* This next line is important, for otherwise the hashtable will keep growing and growing as
new critters appear and generate new keys.  Just calling CMap::RemoveAll() is not enough, as
this call doesn't resize the hash table. The setSizeForCritterCount(crittercount) deletes the old
members of the hash table array, deletes the hash table array, and sets the hash table array size to the
recommended value of the first prime greater than 1.2 times the anticipated number of entries,
which is the square of crittercount. */
	_metric.setSizeForCritterCount(GetSize());
	for (int i=0; i<GetSize(); i++)
	{
		GetAt(i)->setMetricKey(i);
		for (int j=0; j<i; j++) 
		{
			cVector direction = GetAt(j)->position() - GetAt(i)->position();
			Real distance = direction.magnitude();
			if (distance > SMALL_REAL)
				direction /= distance;
			else
				direction = cVector(1,0);
			_metric.setAt(i, j, distance, direction);
			_metric.setAt(j, i, distance, -direction);
		}
	}	
#endif //USEMETRIC
}

Real cBiota::distance(cCritter *pa, cCritter *pb)
{
#ifdef USEMETRIC
	return _metric.distance(pa, pb);
#else //Don't use _metric
	return pa->position().distanceTo(pb->position());
#endif //USEMETRIC
}

cVector cBiota::direction(cCritter *pa, cCritter *pb)
{
#ifdef USEMETRIC
	return _metric.direction(pa, pb);
#else //Don't use _metric
	cVector dir = pb->position() - pa->position();
	dir.normalize();
	return dir;
#endif //USEMETRIC

}

cDistanceAndDirection cBiota::distanceAndDirection(cCritter *pa, cCritter *pb)
{
#ifdef USEMETRIC
	return _metric.distanceanddirection(pa, pb);
#else //Don't use _metric
	cVector dir = pb->position() - pa->position();
	Real distance = dir.magnitude();
	if (distance > SMALL_REAL)
		dir /= distance;
	else
		dir = cVector(1,0); //default unit vector
	return cDistanceAndDirection(distance, dir);
#endif //USEMETRIC
}


		
</pre>

