////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2016 RWS Inc, All Rights Reserved
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of version 2 of the GNU General Public License as published by
// the Free Software Foundation
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
// bulletFest.CPP
// Project: Postal
// 
// History:
//		02/18/97 JMI	Started.
//
//		02/19/97	JMI	Now Ricochet(), Impact(), and Flare() create animations.
//
//		02/19/97	JMI	Now Fire() and FireDeluxe() take all 3 CSmash masks.
//
//		02/19/97	JMI	FireDeluxe(), forces impact, if terrain contact is off
//							of realm.
//
//		02/27/97	JMI	In progress.
//
//		02/27/97	JMI	Experimented with various ways of determining the terrain
//							angle but it is very difficult considering the attribute
//							maps granularity is 8!
//
//		02/27/97	JMI	Forgot to remove feedback flag to preprocessor.
//
//		02/27/97	JMI	Fongoolishness.
//
//		03/03/97	JMI	Now uses QuickCheckClosest() instead of QuickCheck().
//
//		03/05/97	JMI	Fixed rspMod360() usage to current.
//
//		03/10/97	JMI	Added unimplemented UpdateTarget() stub.
//
//		03/21/97	JMI	Added optional tracers.
//
//		03/21/97 BRH	Fixed a small divide by zero error check.
//
//		04/02/97	JMI	Changed ms_ati to m_ati.
//							Also, added ms_u8TracerIndex.
//							Also, removed all the edge info crap.
//							Also, added two additional ways of displaying tracers.
//
//		04/02/97	JMI	Feedback now uses CSpriteLine2d.
//							Also, now uses mapping using 4 clusters &'d together
//							as an index to determine the terrain angle.
//							Does not seem to work.
//
//		04/02/97	JMI	Switched back to METHOD 2 and fixed a divide by zero bug
//							in there.  Also, reduced TRACER_MAX_LENGTH to 3.
//
//		04/10/97 BRH	Updated this to work with the new multi layer attribute
//							maps.
//
//		04/24/97	JMI	Added smid parameter specifying type of ammo noise to
//							FireDeluxe() and Flare().
//
//		04/29/97	JMI	Fire() now allows bullets to go 10 pixels off the realm
//							to make it appear as if the edge of the realm has no
//							affect on them (except the bottom edge which is greater
//							so that, if the bullet is exceptionally high, it will
//							go, hopefully, entirely off screen).
//
//		05/29/97	JMI	Removed references to m_pRealm->m_pAttribMap which no longer
//							exists.
//
//		06/11/97	JMI	Added Preload() for loading assets used during play.
//
//		06/17/97	JMI	Converted all occurrences of rand() to GetRand() and
//							srand() to SeedRand().
//
//		06/26/97	JMI	Now uses pRealm->Map3Dto2D() in Fire() (was using
//							the global version defined in reality.h which is no more).
//
//		06/28/97	JMI	Missed some Map3Dto2D()'s that were #if 0'ed out.
//
//		06/30/97	JMI	Now uses CRealm's new GetRealmWidth() and *Height()
//							for dimensions of realm's X/Z plane.
//
//		07/01/97	JMI	Now bases ricochets on GetRandom().
//
//		07/09/97	JMI	Now uses m_pRealm->Make2dResPath() to get the fullpath
//							for 2D image components.
//
//		07/09/97	JMI	Changed Preload() to take a pointer to the calling realm
//							as a parameter.
//
//		07/18/97	JMI	Got rid of bogus immitation PlaySample functions.
//							Now there is one PlaySample() function.  Also, you now
//							MUST specify a category and you don't have to specify a
//							SoundInstance ptr to specify a volume.
//
//		07/27/97	JMI	No longer calls CRealm::Make2dResPath() on res names
//							given to CAnimThing as it already does that 
//							automatically.
//
//		08/20/97 BRH	Changed ricochet sounds to Weapon volume control
//							instead of destruction.
//
//		09/18/97	JMI	UpdateTracerColor() now takes a realm ptr and now is 
//							color mapped on the first Fire() call.
//
//////////////////////////////////////////////////////////////////////////////
//
// bulletFest handles launching of bullets.  Currently it doesn't seem like
// much more than a function call, but eventually it will need to keep track
// of some per instance data.
//
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
// C Headers -- Must be included before RSPiX.h b/c RSPiX utilizes SHMalloc.
//////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////
// RSPiX Headers.
///////////////////////////////////////////////////////////////////////////////

#include "RSPiX.h"

//////////////////////////////////////////////////////////////////////////////
// Postal headers.
//////////////////////////////////////////////////////////////////////////////
#include "bulletFest.h"
#include "AnimThing.h"
#include "SampleMaster.h"
#include "reality.h"

//////////////////////////////////////////////////////////////////////////////
// Module specific macros.
//////////////////////////////////////////////////////////////////////////////

#define IMPACT_RES_NAME		"Ricochet.aan"
#define RICOCHET_RES_NAME	"Ricochet.aan"
#define FLARE_RES_NAME		"MuzzleFlare.aan"

#define TERRAIN_CHECK_DIST				1
#define TERRAIN_CHECK_GRANULARITY	4

#define TRACER_COLOR_RED		250
#define TRACER_COLOR_GREEN		200
#define TRACER_COLOR_BLUE		50

// The color range that is the same on all hoods.
#define CONSTANT_COLOR_START_INDEX	95
#define NUM_CONSTANT_COLOR_INDICES	(256 - CONSTANT_COLOR_START_INDEX - 10)

#define TRACER_WRAP_DISTANCE	10
#define TRACER_MAX_LENGTH		3

#define LEFT_TOP		0x1
#define RIGHT_TOP		0x2
#define LEFT_BOTTOM	0x4
#define RIGHT_BOTTOM	0x8

// This is done as a mask instead of a mod for speed.
// Note that 0xFFFF is 100% of the time, 0x00FF is 50%, etc.
// Chances are number of set bits out of 16.
#define RICOCHET_CHANCE_MASK	0x0001

#define BULLET_SND_HALF_LIFE	1000

//////////////////////////////////////////////////////////////////////////////
// Module specific typedefs.
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
// Exported (extern) variables.
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
// Module specific (static) variables / Instantiate class statics.
//////////////////////////////////////////////////////////////////////////////

U8	CBulletFest::ms_u8TracerIndex	= 0;	// The color index to use for tracers.
													// This value is gotten only once per
													// execution of this program; therefore,
													// it is gotten from the group of colors
													// that don't change from hood to hood.

// Maps a combination of bits to an angle.
// Each bit set to 1 indicates the terrain in that location is higher than
// the specified height.
// Less than 2 bits set is considered not enough info.
// 2 diagonally adjacent bits is considered not enough info.
static int16_t ms_asTerrainAngleMap[16]	=
	{
	-1,	// 0000	==
	-1,	// 0001	== LEFT_TOP
	-1,	// 0010	== RIGHT_TOP
	0,		// 0011	== RIGHT_TOP | LEFT_TOP
	-1,	// 0100	== LEFT_BOTTOM
	90,	// 0101	== LEFT_BOTTOM | LEFT_TOP
	-1,	// 0110	== LEFT_BOTTOM | RIGHT_TOP
	45,	// 0111	== LEFT_BOTTOM | RIGHT_TOP | LEFT_TOP
	-1,	// 1000	== RIGHT_BOTTOM
	-1,	// 1001	== RIGHT_BOTTOM | LEFT_TOP
	270,	// 1010	== RIGHT_BOTTOM | RIGHT_TOP
	315,	// 1011	== RIGHT_BOTTOM | RIGHT_TOP | LEFT_TOP
	180,	// 1100	== RIGHT_BOTTOM | LEFT_BOTTOM
	135,	// 1101	== RIGHT_BOTTOM | LEFT_BOTTOM | LEFT_TOP
	225,	// 1110	== RIGHT_BOTTOM | LEFT_BOTTOM | RIGHT_TOP
	-1,	// 1111	== RIGHT_BOTTOM | LEFT_BOTTOM | RIGHT_TOP | LEFT_TOP
	};

//////////////////////////////////////////////////////////////////////////////
// Module specific (static) protos.
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
// Functions.
//////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////
// Get max height in a rectangle.
//////////////////////////////////////////////////////////////////////////////
inline
int16_t GetMaxHeight(
	CRealm* pRealm,				// In:  Realm in question.
	int16_t sX,						// In:  X position on terrain.
	int16_t sZ,						// In:  Z position on terrain.
	int16_t	sW,						// In:  Amount in X direction.
	int16_t	sH)						// In:  Amount in Z direction.
	{
	int16_t	sX2	= sX + sW;
	int16_t	sZ2	= sZ + sH;

	int16_t sIterX;
	int16_t	sIterZ;
	int16_t	sMaxHeight	= -32767;
	for (sIterZ = sZ; sIterZ < sZ2; sIterZ++)
		{
		for (sIterX = sX; sIterX < sX2; sIterX++)
			{
			// Relying on NON-macro MAX (macro MAX would evaluate one of the paramters
			// twice).
			sMaxHeight = MAX(sMaxHeight, pRealm->GetHeight(sIterX, sIterZ));
#if 0
			// FEEDBACK.
			// Create a line sprite.
			CSpriteLine2d*	psl2d	= new CSpriteLine2d;
			if (psl2d != NULL)
				{
				psl2d->m_sX2		= sIterX, 
				psl2d->m_sY2		= sIterZ;
				psl2d->m_sX2End	= sIterX + 1; 
				psl2d->m_sY2End	= sIterZ + 1;
				psl2d->m_sPriority	= 0;
				psl2d->m_sLayer		= CRealm::GetLayerViaAttrib(m_pRealm->GetLayer(sIterX, sIterZ));
				psl2d->m_u8Color		= 250;
				// Destroy when done.
				psl2d->m_sInFlags	= CSprite::InDeleteOnRender;
				// Put 'er there.
				pRealm->m_scene.UpdateSprite(psl2d);
				}
#endif
			}
		}

	return sMaxHeight;
	}

//////////////////////////////////////////////////////////////////////////////
// 
// Take a point inside terrain and find our way out via X/Z plane.
// Once out, scan along terrain to determine the angle of the terrain that
// exceeds sY in height.
// ASSUMPTION: sX, sY, sZ is a point inside terrain.
//
//////////////////////////////////////////////////////////////////////////////
inline
bool GetTerrainAngle(			// true, if terrain angle determined.
										// false, if not enough iterations available to
										// determine angle.
	CRealm* pRealm,				// In:  Realm in question.
	int16_t sX,						// In:  X position on terrain.
	int16_t sY,						// In:  Y position on terrain.
	int16_t sZ,						// In:  Z position on terrain.
	int16_t sMaxOutIterations,	// In:  Max iterations to get out of terrain
										// (i.e., find terrain edge).
	int16_t sNumScanIterations,	// In:  Num iterations to scan edge of terrain
										// in EACH direction.
	int16_t	sInAngle,				// In:  Angle from which object entered terrain.
										// This angle + 180 is used to scan out.
	int16_t* psAngle)				// Out: Angle of terrain at specified location
										// on X/Z plane.
	{
	bool bFoundAngle	= false;	// Assume not found.

	// Check 4 surrounding points.
	int16_t	sIndex	= 0;
	if (GetMaxHeight(pRealm, sX - TERRAIN_CHECK_DIST - TERRAIN_CHECK_GRANULARITY / 2, sZ - TERRAIN_CHECK_DIST - TERRAIN_CHECK_GRANULARITY / 2, TERRAIN_CHECK_GRANULARITY, TERRAIN_CHECK_GRANULARITY) > sY)
		sIndex	= LEFT_TOP;
	if (GetMaxHeight(pRealm, sX + TERRAIN_CHECK_DIST + TERRAIN_CHECK_GRANULARITY / 2, sZ - TERRAIN_CHECK_DIST - TERRAIN_CHECK_GRANULARITY / 2, TERRAIN_CHECK_GRANULARITY, TERRAIN_CHECK_GRANULARITY) > sY)
		sIndex	|= RIGHT_TOP;
	if (GetMaxHeight(pRealm, sX - TERRAIN_CHECK_DIST - TERRAIN_CHECK_GRANULARITY / 2, sZ + TERRAIN_CHECK_DIST + TERRAIN_CHECK_GRANULARITY / 2, TERRAIN_CHECK_GRANULARITY, TERRAIN_CHECK_GRANULARITY) > sY)
		sIndex	|= LEFT_BOTTOM;
	if (GetMaxHeight(pRealm, sX + TERRAIN_CHECK_DIST + TERRAIN_CHECK_GRANULARITY / 2, sZ + TERRAIN_CHECK_DIST + TERRAIN_CHECK_GRANULARITY / 2, TERRAIN_CHECK_GRANULARITY, TERRAIN_CHECK_GRANULARITY) > sY)
		sIndex	|= RIGHT_BOTTOM;

	// Get terrain angle.
	*psAngle	= ms_asTerrainAngleMap[sIndex];

	if (*psAngle > -1)
		{
		bFoundAngle	= true;
		}


	return bFoundAngle;
	}

//////////////////////////////////////////////////////////////////////////////
//
// Launch a bullet, create a muzzle flare at the src (sX, sY, sZ), and,
// if no CThing hit, create a ricochet at where the bullet contacted 
// terrain (*psX, *psY, *psZ).
// This same process can be done in pieces with more control to the user
// by calling Fire(), Flare(), and Ricochet().
//
//////////////////////////////////////////////////////////////////////////////
bool CBulletFest::FireDeluxe(	// Returns what and as Fire() would.
	int16_t sAngleY,					// In:  Angle of launch in degrees (on X/Z plane).
	int16_t	sAngleZ,					// In:  Angle of launch in degrees (on X/Y plane).
	int16_t	sX,						// In:  Launch position.
	int16_t	sY,						// In:  Launch position.
	int16_t	sZ,						// In:  Launch position.
	int16_t	sRange,					// In:  Maximum distance.
	CRealm* pRealm,				// In:  Realm in which to fire.
	CSmash::Bits bitsInclude,	// In:  Mask of CSmash masks that this bullet can hit.
	CSmash::Bits bitsDontCare,	// In:  Mask of CSmash masks that this bullet does not care to hit.
	CSmash::Bits bitsExclude,	// In:  Mask of CSmash masks that this bullet cannot hit.
	int16_t	sMaxRicochetAngle,	// In:  Maximum angle with terrain that can cause
										// a ricochet (on X/Z plane).
	int16_t	sMaxRicochets,			// In:  The maximum number of ricochets.
	int16_t* psX,						// Out: Hit position.
	int16_t* psY,						// Out: Hit position.
	int16_t* psZ,						// Out: Hit position.
	CThing** ppthing,				// Out: Ptr to thing hit or NULL.
	bool	bTracer /*= true*/,	// In:  Draw a tracer at random point along path.
	SampleMasterID	smid	/*= g_smidBulletFire*/)	// In:  Use ammo sample.
	{
	bool bHit	= false;	// Assume no collision with CThing within u32ThingMask.

	// Create a muzzle flare.
	Flare(sAngleY, sX, sY, sZ, pRealm, smid);

	int16_t	sRicochets		= 0;
	bool	bImpact			= false;
	int16_t	sTerrainAngle	= 0;
	// While no hits and not out of ricochets . . .
	while (bHit == false && sRicochets <= sMaxRicochets && bImpact == false)
		{
		sAngleY	= rspMod360(sAngleY);

		bHit	= Fire(
			sAngleY,			// In:  Angle of launch in degrees (on X/Z plane).
			sAngleZ,			// In:  Angle of launch in degrees (on X/Y plane).
			sX,				// In:  Launch position.
			sY,				// In:  Launch position.
			sZ,				// In:  Launch position.
			sRange,			// In:  Maximum distance.
			pRealm,			// In:  Realm in which to fire.
			bitsInclude,	// In:  Mask of CSmash masks that this bullet can hit.
			bitsDontCare,	// In:  Mask of CSmash masks that this bullet does not care to hit.
			bitsExclude,	// In:  Mask of CSmash masks that this bullet cannot hit.
			psX,				// Out: Hit position.
			psY,				// Out: Hit position.
			psZ,				// Out: Hit position.
			ppthing,			// Out: Ptr to thing hit or NULL.
			bTracer);		// In:  Draw a tracer at random point along path.

		// If no hit . . .
		if (bHit == false)
			{
			// If on screen . . .
			if (	*psX > 0 && *psX < pRealm->GetRealmWidth() 
				&& *psZ > 0 && *psZ - *psY < pRealm->GetRealmHeight() )
				{
#if 0
				// If out of ricochets . . .
				if (sRicochets >= sMaxRicochets)
					{
					// Impact.
					bImpact	= true;
					}
				else
					{
					if (GetTerrainAngle(pRealm, *psX, *psY, *psZ, 10, 10, sAngleY, &sTerrainAngle) == true)
						{
						// Check for ricochet.  If our contact angle is within sMaxRicochetAngle
						// of terrain angle . . .
						short	sAngleDif	= sTerrainAngle - sAngleY;
						if (ABS(sAngleDif) > sMaxRicochetAngle)
							{
							bImpact	= true;
							}
						else
							{
							sAngleY	= sTerrainAngle + sAngleDif;
							}
	
#if 0
						// Might have to break this into four quadrants.

						// This works for the case when sAngle is in quadrant 1
						// and sTerrainAngle is in quadrant 2.
						short sAngleDif	= (180 - sTerrainAngle) + sAngleY;

						if (sAngleDif > 90)
							{
							sAngleDif = 180 - sAngleDif;
							}

						if (sAngleDif > sMaxRicochetAngle)
							{
							// Impact.
							bImpact	= true;
							}
						else	// Ricochet.
							{
							// **** Determine deflection angle here.
							sAngleY	= 360 - sAngleDif;
							}
#endif
						}
					else
						{
						bImpact	= true;
						}
					}

				// If impacting . . .
				if (bImpact == true)
					{
					// Impact at angle of shot.
					Impact(sAngleY, *psX, *psY, *psZ, pRealm);
					}
				else
					{
					// Ricochet at angle of deflection.

					Ricochet(sAngleY, *psX, *psY, *psZ, pRealm);

					sX	= *psX;
					sY	= *psY;
					sZ	= *psZ;
					}
#else	
				// Ricochet is random.
				if (GetRandom() & RICOCHET_CHANCE_MASK)
					{
					// Impact at angle of shot.
					Impact(sAngleY, *psX, *psY, *psZ, pRealm);
					}
				else
					{
					// Ricochet at angle of deflection.
					Ricochet(sAngleY, *psX, *psY, *psZ, pRealm);
					}

				// Always stop though.
				bImpact	= true;
#endif
				}
			}

		sRicochets++;
		}

	return bHit;
	}

//////////////////////////////////////////////////////////////////////////////
//
// Create a muzzle flare effect.
//
//////////////////////////////////////////////////////////////////////////////
void CBulletFest::Flare(		// Returns nothing.
	int16_t sAngle,					// In:  Angle of launch in degrees (on X/Z plane).
	int16_t	sX,						// In:  Launch position.
	int16_t	sY,						// In:  Launch position.
	int16_t	sZ,						// In:  Launch position.
	CRealm* pRealm,				// In:  Realm in which to fire.
	SampleMasterID	smid	/*= g_smidBulletFire*/)	// In:  Use ammo sample.
	{
	// Create the animator . . .
	CAnimThing*	pat	= new CAnimThing(pRealm);
	ASSERT(pat != NULL);

	strcpy(pat->m_szResName, FLARE_RES_NAME);

	// Start it up:
	// No looping.
	pat->m_sLoop	= FALSE;
	// No notification necessary.
	// NOTE:  sAngle is currently not utilized.
	pat->Setup(sX, sY, sZ);

	// todo: clean up
	pat->m_paachannel->id = 6;
	

	// Start sample.
	PlaySample(
		smid,
		SampleMaster::Weapon,
		DistanceToVolume(sX, sY, sZ, BULLET_SND_HALF_LIFE) );
	}

//////////////////////////////////////////////////////////////////////////////
//
// Create a impact effect.
//
//////////////////////////////////////////////////////////////////////////////
void CBulletFest::Impact(	// Returns nothing.
	int16_t sAngle,				// In:  Angle of launch in degrees (on X/Z plane).
	int16_t	sX,					// In:  Launch position.
	int16_t	sY,					// In:  Launch position.
	int16_t	sZ,					// In:  Launch position.
	CRealm* pRealm)			// In:  Realm in which to fire.
	{
	// Create the animator . . .
	CAnimThing*	pat	= new CAnimThing(pRealm);
	ASSERT(pat != NULL);

	strcpy(pat->m_szResName, IMPACT_RES_NAME);

	// Start it up:
	// No looping.
	pat->m_sLoop	= FALSE;
	// No notification necessary.
	// NOTE:  sAngle is currently not utilized.
	pat->Setup(sX, sY, sZ);

	// todo: clean up
	pat->m_paachannel->id = 5;
	
	}

//////////////////////////////////////////////////////////////////////////////
//
// Create a ricochet effect.
//
//////////////////////////////////////////////////////////////////////////////
void CBulletFest::Ricochet(	// Returns nothing.
	int16_t sAngle,					// In:  Angle of launch in degrees (on X/Z plane).
	int16_t	sX,						// In:  Launch position.
	int16_t	sY,						// In:  Launch position.
	int16_t	sZ,						// In:  Launch position.
	CRealm* pRealm)				// In:  Realm in which to fire.
	{
	// Create the animator . . .
	CAnimThing*	pat	= new CAnimThing(pRealm);
	ASSERT(pat != NULL);

	strcpy(pat->m_szResName, RICOCHET_RES_NAME);

	// Start it up:
	// No looping.
	pat->m_sLoop	= FALSE;
	// No notification necessary.
	// NOTE:  sAngle is currently not utilized.
	pat->Setup(sX, sY, sZ);

	// todo: clean up
	pat->m_paachannel->id = 5;
	

	// Start sample.
	PlaySample(
		(GetRand() % 2) ? g_smidRicochet1 : g_smidRicochet2,
		SampleMaster::Weapon,
		DistanceToVolume(sX, sY, sZ, BULLET_SND_HALF_LIFE) );
		
	}

//////////////////////////////////////////////////////////////////////////////
//
// Launch a bullet.
//
//////////////////////////////////////////////////////////////////////////////
bool CBulletFest::Fire(			// Returns true if a hit, false otherwise.
	int16_t sAngleY,					// In:  Angle of launch in degrees (on X/Z plane).
	int16_t	sAngleZ,					// In:  Angle of launch in degrees (on X/Y plane).
	int16_t	sX,						// In:  Launch position.
	int16_t	sY,						// In:  Launch position.
	int16_t	sZ,						// In:  Launch position.
	int16_t	sRange,					// In:  Maximum distance.
	CRealm* pRealm,				// In:  Realm in which to fire.
	CSmash::Bits bitsInclude,	// In:  Mask of CSmash masks that this bullet can hit.
	CSmash::Bits bitsDontCare,	// In:  Mask of CSmash masks that this bullet does not care to hit.
	CSmash::Bits bitsExclude,	// In:  Mask of CSmash masks that this bullet cannot hit.
	int16_t* psX,						// Out: Hit position.
	int16_t* psY,						// Out: Hit position.
	int16_t* psZ,						// Out: Hit position.
	CThing** ppthing,				// Out: Ptr to thing hit or NULL.
	bool	bTracer /*= true*/)	// In:  Draw a tracer at random point along path.
	{
	bool bHit	= false;	// Assume no collision with CThing within u32ThingMask.

	// If tracer color not yet color mapped . . .
	if (ms_u8TracerIndex == 0)
		{
		UpdateTracerColor(pRealm);
		}
		
	/////////// Determine length of travel if no CThings hit ///////////////////

	// Get most efficient increments that won't miss any attributes.
	// For the rates we use trig with a hypotenuse of 1 which will give
	// us a rate <= 1.0 and then multiply by the scaling of the map for
	// a reasonable doubling or so of the speed of this alg.
	
	// sAngle must be between 0 and 359.
	sAngleY	= rspMod360(sAngleY);
	sAngleZ	= rspMod360(sAngleZ);

	float sinp;
	float cosp;

	rspSinCos(float(sAngleY), &sinp, &cosp);
	float	fRateX		= cosp/* * pRealm->m_pAttribMap->m_sScaleX*/;
	float	fRateZ		= -sinp/* * pRealm->m_pAttribMap->m_sScaleY*/;

	rspSinCos(float(sAngleZ), &sinp, &cosp);
	float	fRateY		= sinp;		

	// Determine amount traveled per iteration just once.
	float	fIterDist	= rspSqrtf(ABS2(fRateX, fRateY, fRateZ));

	// Set initial position to first point to check (NEVER checks original position).
	float	fPosX			= sX + fRateX;
	float	fPosY			= sY + fRateY;
	float	fPosZ			= sZ + fRateZ;

	float	fTotalDist	= 0.0F;

	// Store extents allowing bullets to go 10 pixels off each boundary.
	// This makes them appear to be unhindered by the edge of the realm.
	// (except the bottom edge which is greater so that, if the bullet 
	// is exceptionally high, it will go, hopefully, entirely off screen).	
	int16_t	sMaxX			= pRealm->GetRealmWidth() + 10;
	int16_t	sMaxY			= 512;					// Robustness: Guard against infinite loop
														// in the remote possibility that we shoot
														// straight up (currently one can only shoot
														// horizontally).
	int16_t	sMaxZ			= pRealm->GetRealmHeight() + sY + 10;

	int16_t	sMinX			= -10;
	int16_t	sMinY			= -512;
	int16_t	sMinZ			= -10;

	int16_t	sCurH;

	// Scan while in realm.
	while (
			fPosX > sMinX 
		&& fPosY > sMinY 
		&& fPosZ > sMinZ 
		&& fPosX < sMaxX 
		&& fPosY < sMaxY 
		&& fPosZ < sMaxZ)
		{
		sCurH = pRealm->GetHeight((int16_t) fPosX, (int16_t) fPosZ);
		// If bullet below or at terrain . . .
		if (fPosY <=  sCurH)
			{
			break;
			}

		// If we've exceeded range . . .
		if (fTotalDist > sRange)
			{
			// Drop bullet via gravity.
			fPosY	-= 1.0;			// FUDGE!
			}

		fPosX	+= fRateX;
		fPosY	+= fRateY;
		fPosZ	+= fRateZ;
		fTotalDist	+= fIterDist;
		}

	// Create 3D line segment.
	R3DLine	line;
	line.X1	= sX;
	line.Y1	= sY;
	line.Z1	= sZ;
	line.X2	= fPosX;
	line.Y2	= fPosY;
	line.Z2	= fPosZ;

	// Determine if anything with mask u32ThingMask was hit . . .
	CSmash*	psmash;
	if (pRealm->m_smashatorium.QuickCheckClosest(
		&line, 
		bitsInclude, 
		bitsDontCare, 
		bitsExclude, 
		&psmash) == true)
		{
		// Set *ppthing to thing hit.
		*ppthing	= psmash->m_pThing;

		bHit	= true;

		// Set end pt.
		*psX		= psmash->m_sphere.sphere.X;
		*psY		= psmash->m_sphere.sphere.Y;
		*psZ		= psmash->m_sphere.sphere.Z;
		}
	else
		{
		// Clear thing ptr.
		*ppthing	= NULL;
		// Set end pt.
		*psX		= fPosX;
		*psY		= fPosY;
		*psZ		= fPosZ;
		}

	if (bTracer == true)
		{
		// Create a line sprite.
		CSpriteLine2d*	psl2d	= new CSpriteLine2d;
		if (psl2d != NULL)
			{
			// Get distance.  Since an interception may have occurred, we need to redetermine the
			// distance.
			// We use one coordinate of the end point against one coordinate of the interception point
			// to get a ratio and apply that to the total distance.
			int16_t	sDistance	= 0;
			if (fPosX - sX != 0.0)
				{
				sDistance	= fTotalDist * ((*psX - sX) / (fPosX - sX));
				}

#define METHOD	2			
#define TRACER_ACCUMMULATION_STEP_MAX	80
#define TRACER_ACCUMMULATION_STEP_MIN	10

			int16_t	sRand		= 0;
#if METHOD == 1	// Method 1:  Random dist between here and there.
			
			if (sDistance != 0)
				{
				sRand	= GetRand() % sDistance;
				}

#elif METHOD == 2	// Method 2:  Cummulating position with wrapping.

			if (sDistance != 0)
				{
				sRand	= m_sCurTracerPos	= (m_sCurTracerPos + GetRand() % TRACER_ACCUMMULATION_STEP_MAX) % sDistance;
				}

#else	METHOD == 3	// Method 3:  Multiple Method 1 and Method 2's.
			
			// Reduce distance.
			sDistance	-= TRACER_ACCUMMULATION_STEP_MAX;

			while (sRand < sDistance)
				{
				sRand	+= (GetRand() % (TRACER_ACCUMMULATION_STEP_MAX - TRACER_ACCUMMULATION_STEP_MIN)) + TRACER_ACCUMMULATION_STEP_MIN;
				// If no current sprite . . . 
				if (psl2d == NULL)
					{
					// Create a line sprite.
					psl2d	= new CSpriteLine2d;
					}

				// If we have a sprite . . .
				if (psl2d != NULL)
					{
#endif

			int16_t	sRand2	= GetRand() % TRACER_MAX_LENGTH;

			float	fStartX	= sX + fRateX * sRand;
			float	fStartY	= sY + fRateY * sRand;
			float	fStartZ	= sZ + fRateZ * sRand;

			pRealm->Map3Dto2D(
				fStartX, 
				fStartY, 
				fStartZ, 
				&(psl2d->m_sX2), 
				&(psl2d->m_sY2) );
			pRealm->Map3Dto2D(
				fStartX + fRateX * sRand2, 
				fStartY + fRateY * sRand2, 
				fStartZ + fRateZ * sRand2, 
				&(psl2d->m_sX2End), 
				&(psl2d->m_sY2End) );
			psl2d->m_sPriority	= fStartZ;
			psl2d->m_sLayer		= CRealm::GetLayerViaAttrib(pRealm->GetLayer((int16_t) fStartX, (int16_t) fStartZ));
			psl2d->m_u8Color		= ms_u8TracerIndex;
			// Destroy when done.
			psl2d->m_sInFlags	= CSprite::InDeleteOnRender;
			// Put 'er there.
			pRealm->m_scene.UpdateSprite(psl2d);
#if METHOD == 3
					
					// Done with this 'sprite'.
					psl2d	= NULL;
					}
				}
#endif
			}
		else
			{
			TRACE("Fire(): Unable to allocate CSpriteLine2d.\n");
			}
		}

#if 0
	// FEEDBACK.
	// Create a line sprite.
	CSpriteLine2d*	psl2d	= new CSpriteLine2d;
	if (psl2d != NULL)
		{
		pRealm->Map3Dto2D(
			sX, 
			sY, 
			sZ, 
			&(psl2d->m_sX2), 
			&(psl2d->m_sY2) );
		pRealm->Map3Dto2D(
			sX + fRateX * fTotalDist, 
			sY + fRateY * fTotalDist, 
			sZ + fRateZ * fTotalDist, 
			&(psl2d->m_sX2End), 
			&(psl2d->m_sY2End) );
		psl2d->m_sPriority	= sZ;
		psl2d->m_sLayer		= CRealm::GetLayerViaAttrib(pRealm->GetLayer(sX, sZ));
		psl2d->m_u8Color		= ms_u8TracerIndex;
		// Destroy when done.
		psl2d->m_sInFlags	= CSprite::InDeleteOnRender;
		// Put 'er there.
		pRealm->m_scene.UpdateSprite(psl2d);
		}
#endif

	return bHit;
	}

///////////////////////////////////////////////////////////////////////////////
// Update current targeting information.  This will function will
// continually track the position of a target in order to determine
// how much it is moving.  The idea being that targets that move 
// irratically(sp?) and more often are harder to hit.
///////////////////////////////////////////////////////////////////////////////
void CBulletFest::UpdateTarget(	// Returns nothing.
	int16_t sAngle,						// In:  Angle of aim in degrees (on X/Z plane).
	int16_t	sX,							// In:  Aim position.
	int16_t	sY,							// In:  Aim position.
	int16_t	sZ,							// In:  Aim position.
	CRealm* pRealm)					// In:  Realm in which to target.
	{
	// NYI!
	}


// todo: clean up
extern PVR::cRenderMgr *g_pvrRenderer;

///////////////////////////////////////////////////////////////////////////////
// Updates the static tracer color.
// (static)
///////////////////////////////////////////////////////////////////////////////
void CBulletFest::UpdateTracerColor(
	CRealm* prealm)							// In:  Calling realm.
	{

	ASSERT(prealm->m_phood);
	ASSERT(g_pvrRenderer->m_pPaletteDefault);

	RPal*	ppal	= g_pvrRenderer->m_pPaletteDefault;

	ms_u8TracerIndex	= rspMatchColorRGB(	// Out: Matched index.
		TRACER_COLOR_RED,							// In:  Pixel's red value.                                 
		TRACER_COLOR_GREEN,						// In:  Pixel's green value.                               
		TRACER_COLOR_BLUE,						// In:  Pixel's blue value.                                
		CONSTANT_COLOR_START_INDEX,			// In:  Min mappable index (affects source).   
		// Do NOT include the top 10 windows colors!
		NUM_CONSTANT_COLOR_INDICES,			// In:  Num mappable indices (affects source). 
		ppal->Red(0),								// In:  Beginning of red color table.                      
		ppal->Green(0),							// In:  Beginning of green color table.                    
		ppal->Blue(0),								// In:  Beginning of blue color table.                     
		ppal->GetPalEntrySize()					// In:  Size to increment between each index in each table.
		);

	}

///////////////////////////////////////////////////////////////////////////////
// Preload any assets that may be used.
// (static).
///////////////////////////////////////////////////////////////////////////////
int16_t CBulletFest::Preload(
	CRealm* prealm)				// In:  Calling realm.
	{
	CAnimThing::ChannelAA*	paaCache;
	int16_t	sResult	= 0;

	if (rspGetResource(&g_resmgrGame, prealm->Make2dResPath(IMPACT_RES_NAME), &paaCache) == 0)
		rspReleaseResource(&g_resmgrGame, &paaCache);
	else
		sResult |= 1;

	if (rspGetResource(&g_resmgrGame, prealm->Make2dResPath(RICOCHET_RES_NAME), &paaCache) == 0)
		rspReleaseResource(&g_resmgrGame, &paaCache);
	else
		sResult |= 1;
		
	if (rspGetResource(&g_resmgrGame, prealm->Make2dResPath(FLARE_RES_NAME), &paaCache) == 0)
		rspReleaseResource(&g_resmgrGame, &paaCache);
	else
		sResult |= 1;

	return sResult;
	}

///////////////////////////////////////////////////////////////////////////////
// EOF
///////////////////////////////////////////////////////////////////////////////
