////////////////////////////////////////////////////////////////////////////////
//
// 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
//
// fireball.cpp
// Project: Nostril (aka Postal)
//
// This module implements the CFireball weapon class which is a burning flame
//	for several different effects and weapons.
// 
//
// History:
//		01/17/97 BRH	Started this fireball ammo for use in making a flamethrower
//
//		04/25/97	JMI	PreLoad() was not properly assigning into sResult.
//
//		04/29/97	JMI	Changed name of ProcessMessages() to 
//							ProcessFireballMessages() to avoid conflicts with 
//							CWeapon's ProcessMessages().
//							Also, Update() was not moving the m_smash (it was merely
//							set the one time in Init()) which was causing it to not
//							collide with anything (I guess it might've collided with
//							things hovering around the upper, left corner of the realm).
//
//		05/29/97	JMI	Removed ASSERT on m_pRealm->m_pAttribMap which no longer
//							exists.
//
//		06/11/97 BRH	Passed on shooter ID to the message it sends when it burns
//							someone.
//
//		06/17/97	JMI	Converted all occurrences of rand() to GetRand() and
//							srand() to SeedRand().
//
//		07/02/97 BRH	Added CFirestream object that creates several CFireball
//							objects.
//
//		07/04/97 BRH	Added auto alpha based on the time to live.
//
//		07/07/97 BRH	Fixed casing on MIN to compile on both PC and Mac.
//
//		07/08/97	JMI	Put in code in Render() to avoid an unlikely divide by zero.
//							Still have a divide by zero release mode problem though.
//							Also, there was initialization typo in CFireball::Setup().
//
//		07/08/97	JMI	Fixed Render() to distribute the homogeneous alpha level
//							better.  Still needs tuning.
//
//					JMI	Made heavier again.
//
//		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/10/97	JMI	Now uses alpha mask and level for animation.
//
//		07/12/97 BRH	Added IsPathClear check to path of fireball since its
//							velocity is so high that it can pass through walls
//							otherwise.
//
//		07/13/97 BRH	Added a check to the Firestream creation of the
//							fireballs to make sure that the iniial position is
//							not in a wall.  If it is, then the other two fireballs
//							with a further offset should not be created, otherwise
//							it will shoot through walls.  
//
//							Also changed the alpha to use 1 mask and tuned the levels
//							so that you can see it at the gun end.
//
//		07/14/97	JMI	Now will detach itself from its parent via 
//							m_sprite.m_psprParent->RemoveChild() in the rare event 
//							that it is still parented going into the Render().
//							Also, threw in some random to the m_dRot before they are
//							separated from the CFirestream.
//
//		07/27/97	JMI	Changed to use Z position (i.e., X/Z plane) instead of
//							Y2 position (i.e., viewing plane) position for draw 
//							priority.
//
//		07/30/97	JMI	Changed several deletes that occurred just before reading
//							instantiable member variables.  Now both 
//							ProcessFireballMessage() functions (there are two (one for
//							CFireballs and one for CFireStreams) ) do NOT delete this
//							when they receive a delete message.  They instead set the
//							state to delete and allow the calling function to do the
//							delete.  The problem here was that the calling function,
//							Update(), needed to look at one more member var, m_eState,
//							before returning, but, at that point (that is, after the
//							delete) this was invalid.  This works find though when
//							using SmartHeap I think b/c of SmartHeap's bounds checking
//							areas to detect overwrites.  The Alpha does not have these
//							areas (no SmartHeap) and, so, crashes easily in these cases.
//							There was also a problem in Update() where it deleted this
//							but did not return right away and one more member var access
//							was done after that point (probably added later).  Fixed.
//
//		07/30/97	JMI	Now uses m_dHorizVel for its velocity which is initially 
//							set to ms_dFireVelocity but can be overridden after the
//							Setup() call.
//
//		08/08/97	JMI	Now CFirestream::ProcessFireballMessages() passes on
//							delete messages to any fireballs it owns.
//
//		08/08/97	JMI	Changed m_pFireball1, 2, & 3 to m_idFireball1, 2, & 3.
//
////////////////////////////////////////////////////////////////////////////////
#define FIREBALL_CPP

#include "RSPiX.h"
#include <math.h>

#include "fireball.h"
#include "game.h"
#include "reality.h"

#include "pvr.h"

extern PVR::cRenderMgr* g_pvrRenderer;

////////////////////////////////////////////////////////////////////////////////
// Macros/types/etc.
////////////////////////////////////////////////////////////////////////////////

#define SMALL_FILE			"tinyfire.aan"

#define MIN_ALPHA				30
#define MAX_ALPHA				200
// Note the sum of these two values should not exceed 255

// Gets a random between -range / 2 and range / 2.
#define RAND_SWAY(sway)	((GetRand() % sway) - sway / 2)

#define FIREBALL_SWAY		15

////////////////////////////////////////////////////////////////////////////////
// Variables/data
////////////////////////////////////////////////////////////////////////////////

// Let this auto-init to 0
int16_t CFirestream::ms_sFileCount;
int16_t CFirestream::ms_sOffset1 = 12;		// pixels from 1st to 2nd fireball
int16_t CFirestream::ms_sOffset2 = 24;	// pixels from 1st to 3rd fireball

////////////////////////////////////////////////////////////////////////////////
// Load object (should call base class version!)
////////////////////////////////////////////////////////////////////////////////
int16_t CFirestream::Load(										// Returns 0 if successfull, non-zero otherwise
	RFile* pFile,											// In:  File to load from
	bool bEditMode,										// In:  True for edit mode, false otherwise
	int16_t sFileCount,										// In:  File count (unique per file, never 0)
	uint32_t	ulFileVersion)									// In:  Version of file format to load.
	{
	int16_t sResult = CThing::Load(pFile, bEditMode, sFileCount, ulFileVersion);

	if (sResult == 0)
	{
		// Load common data just once per file (not with each object)
		if (ms_sFileCount != sFileCount)
		{
			ms_sFileCount = sFileCount;

			// Load static data.
			switch (ulFileVersion)
			{
				default:
				case 1:
					break;
			}
		}

		// Load instance data.
		switch (ulFileVersion)
		{
			default:
			case 1:
				break;
		}

		// Make sure there were no file errors or format errors . . .
		if (!pFile->Error() && sResult == 0)
		{
		}
		else
		{
			sResult = -1;
			TRACE("CFirestream::Load(): Error reading from file!\n");
		}
	}
	else
	{
		TRACE("CFirestream::Load():  CThing::Load() failed.\n");
	}

	return sResult;
}

////////////////////////////////////////////////////////////////////////////////
// Startup object
////////////////////////////////////////////////////////////////////////////////
int16_t CFirestream::Startup(void)								// Returns 0 if successfull, non-zero otherwise
{
	return Init();
}

////////////////////////////////////////////////////////////////////////////////
// Shutdown object
////////////////////////////////////////////////////////////////////////////////
int16_t CFirestream::Shutdown(void)							// Returns 0 if successfull, non-zero otherwise
{
	return 0;
}

////////////////////////////////////////////////////////////////////////////////
// Suspend object
////////////////////////////////////////////////////////////////////////////////
void CFirestream::Suspend(void)
{
	m_sSuspend++;
}

////////////////////////////////////////////////////////////////////////////////
// Resume object
////////////////////////////////////////////////////////////////////////////////
void CFirestream::Resume(void)
{
	m_sSuspend--;

	// If we're actually going to start updating again, we need to reset
	// the time so as to ignore any time that passed while we were suspended.
	// This method is far from precise, but I'm hoping it's good enough.
	if (m_sSuspend == 0)
		m_lPrevTime = m_pRealm->m_time.GetGameTime();
}


////////////////////////////////////////////////////////////////////////////////
// Update object
////////////////////////////////////////////////////////////////////////////////
void CFirestream::Update(void)
{
	int32_t lThisTime;

	if (!m_sSuspend)
	{
		lThisTime = m_pRealm->m_time.GetGameTime();

		// See if we killed ourselves
		if (ProcessFireballMessages() == State_Deleted)
			{
			delete this;
			return;
			}

		CFireball*	pfireball1	= NULL;
		CFireball*	pfireball2	= NULL;
		CFireball*	pfireball3	= NULL;

		// Update the other fireballs
		if (m_pRealm->m_idbank.GetThingByID((CThing**)&pfireball1, m_idFireball1) == 0)
			{
			pfireball1->m_dX		= m_dX;
			pfireball1->m_dY		= m_dY;
			pfireball1->m_dZ		= m_dZ;
			pfireball1->m_dRot	= rspMod360(m_dRot + RAND_SWAY(FIREBALL_SWAY) );
			}
		else
			{
			m_idFireball1	= CIdBank::IdNil;
			}

		if (m_pRealm->m_idbank.GetThingByID((CThing**)&pfireball2, m_idFireball2) == 0)
			{
			float cosp;
			float sinp;
			rspSinCos(m_dRot, &sinp, &cosp);

			pfireball2->m_dX		= m_dX + cosp * ms_sOffset1;
			pfireball2->m_dY		= m_dY;
			pfireball2->m_dZ		= m_dZ - sinp * ms_sOffset1;
			pfireball2->m_dRot	= rspMod360(m_dRot + RAND_SWAY(FIREBALL_SWAY) );
			}
		else
			{
			m_idFireball2	= CIdBank::IdNil;
			}

		if (m_pRealm->m_idbank.GetThingByID((CThing**)&pfireball3, m_idFireball3) == 0)
			{
			float cosp;
			float sinp;
			rspSinCos(m_dRot, &sinp, &cosp);
			pfireball3->m_dX		= m_dX + cosp * ms_sOffset2;
			pfireball3->m_dY		= m_dY;
			pfireball3->m_dZ		= m_dZ - sinp * ms_sOffset2;
			pfireball3->m_dRot	= rspMod360(m_dRot + RAND_SWAY(FIREBALL_SWAY) );
			}
		else
			{
			m_idFireball3	= CIdBank::IdNil;
			}

		if (m_eState == CWeapon::State_Fire)
		{
			if (pfireball1)
				pfireball1->m_eState = CWeapon::State_Fire;
			if (pfireball2)
				pfireball2->m_eState = CWeapon::State_Fire;
			if (pfireball3)
				pfireball3->m_eState = CWeapon::State_Fire;
			delete this;
			return;
		}
	}
}


////////////////////////////////////////////////////////////////////////////////
// Render object
////////////////////////////////////////////////////////////////////////////////
void CFirestream::Render(void)
{
	// If we have a parent . . .
	if (m_sprite.m_psprParent)
		{
		// Get outta there!
		m_sprite.m_psprParent->RemoveChild(&m_sprite);
		}

	// This should never ever be rendered.
	ASSERT(m_sprite.m_psprParent == NULL);
	ASSERT( (m_sprite.m_sInFlags & CSprite::PrivInserted) == 0);
}

////////////////////////////////////////////////////////////////////////////////
// Setup
////////////////////////////////////////////////////////////////////////////////

int16_t CFirestream::Setup(									// Returns 0 if successfull, non-zero otherwise
	int16_t sX,												// In:  New x coord
	int16_t sY,												// In:  New y coord
	int16_t sZ,												// In:  New z coord
	int16_t sDir,												// In:  Direction of travel
	int32_t lTimeToLive,										// In:  Number of milliseconds to burn, default 1sec
	U16 u16ShooterID)										// In:  Shooter's ID so you don't hit him
{
	int16_t sResult = 0;
	double dX;
	double dZ;
	
	// Use specified position
	m_dX = (double)sX;
	m_dY = (double)sY;
	m_dZ = (double)sZ;
	m_dRot = sDir;
	m_lPrevTime = m_pRealm->m_time.GetGameTime();
	m_u16ShooterID = u16ShooterID;

	// Make sure that the starting positions are valid before creating
	// fireballs here, otherwise they will shoot through walls.

	float cosp;
	float sinp;
	rspSinCos(m_dRot, &sinp, &cosp);
	dX = m_dX + cosp * ms_sOffset2;	// Second interval 
	dZ = m_dZ - sinp * ms_sOffset2;	
	if (m_pRealm->IsPathClear(		// Returns true, if the entire path is clear.                 
											// Returns false, if only a portion of the path is clear.     
											// (see *psX, *psY, *psZ).                                    
			(int16_t) m_dX, 				// In:  Starting X.                                           
			(int16_t) m_dY, 				// In:  Starting Y.                                           
			(int16_t) m_dZ, 				// In:  Starting Z.                                           
			3.0, 							// In:  Rate at which to scan ('crawl') path in pixels per    
											// iteration.                                                 
											// NOTE: Values less than 1.0 are inefficient.                
											// NOTE: We scan terrain using GetHeight()                    
											// at only one pixel.                                         
											// NOTE: We could change this to a speed in pixels per second 
											// where we'd assume a certain frame rate.                    
			(int16_t) dX,		 			// In:  Destination X.                                        
			(int16_t) dZ,					// In:  Destination Z.                                        
			0,								// In:  Max traverser can step up.                      
			NULL,							// Out: If not NULL, last clear point on path.                
			NULL,							// Out: If not NULL, last clear point on path.                
			NULL,							// Out: If not NULL, last clear point on path.                
			false) )						// In:  If true, will consider the edge of the realm a path
											// inhibitor.  If false, reaching the edge of the realm    
											// indicates a clear path.                                 
	{
		m_lTimeToLive = m_pRealm->m_time.GetGameTime() + lTimeToLive;

		CFireball*	pfireball;
		if (CThing::ConstructWithID(CThing::CFireballID, m_pRealm, (CThing**) &pfireball) == 0)
		{
			pfireball->Setup(m_dX, m_dY, m_dZ, sDir, lTimeToLive, u16ShooterID);
			m_idFireball1	= pfireball->GetInstanceID();
		}

		float cosp;
		float sinp;
		rspSinCos(m_dRot, &sinp, &cosp);

		dX = m_dX + cosp * ms_sOffset1;	// First interval
		dZ = m_dZ - sinp * ms_sOffset1;	
		if (CThing::ConstructWithID(CThing::CFireballID, m_pRealm, (CThing**) &pfireball) == 0)
		{
			pfireball->Setup(dX, m_dY, dZ, sDir, lTimeToLive, u16ShooterID);
			m_idFireball2	= pfireball->GetInstanceID();
		}

		dX = m_dX + cosp * ms_sOffset2;	// Second interval 
		dZ = m_dZ - sinp * ms_sOffset2;	
		if (CThing::ConstructWithID(CThing::CFireballID, m_pRealm, (CThing**) &pfireball) == 0)
		{
			pfireball->Setup(dX, m_dY, dZ, sDir, lTimeToLive, u16ShooterID);
			m_idFireball3	= pfireball->GetInstanceID();
		}

		if (sResult == SUCCESS)
			sResult = Init();
	}

	return sResult;

}

////////////////////////////////////////////////////////////////////////////////
// Init
////////////////////////////////////////////////////////////////////////////////

int16_t CFirestream::Init(void)
{
	int16_t sResult = SUCCESS;	
	return sResult;
}

////////////////////////////////////////////////////////////////////////////////
// Preload - basically trick the resource manager into caching resources for fire
//				 animations before play begins so that when a fire is set for
//				 the first time, there won't be a delay while it loads.
////////////////////////////////////////////////////////////////////////////////

int16_t CFirestream::Preload(
	CRealm* /*prealm*/)			// In:  Calling realm.
{
return 0;
}


////////////////////////////////////////////////////////////////////////////////
// ProcessFireballMessages
////////////////////////////////////////////////////////////////////////////////

CFirestream::CFirestreamState CFirestream::ProcessFireballMessages(void)
{
	CFirestreamState eNewState = State_Idle;

	GameMessage msg;

	if (m_MessageQueue.DeQ(&msg) == true)
	{
		switch(msg.msg_Generic.eType)
		{
			case typeObjectDelete:
				{
				// Pass the message on . . .
				SendThingMessage(&msg, m_idFireball1);
				SendThingMessage(&msg, m_idFireball2);
				SendThingMessage(&msg, m_idFireball3);

				m_MessageQueue.Empty();
				return State_Deleted;
				break;
				}
		}
	}
	// Dump the rest of the messages
	m_MessageQueue.Empty();

	return eNewState;
}



////////////////////////////////// Fireball ////////////////////////////////////


////////////////////////////////////////////////////////////////////////////////
// Macros/types/etc.
////////////////////////////////////////////////////////////////////////////////

#define SMALL_FILE			"tinyfire.aan"

////////////////////////////////////////////////////////////////////////////////
// Variables/data
////////////////////////////////////////////////////////////////////////////////

// Let this auto-init to 0
int16_t CFireball::ms_sFileCount;
int16_t CFireball::ms_sSmallRadius = 8;
int32_t  CFireball::ms_lCollisionTime = 250;			// Check for collisions this often
double CFireball::ms_dFireVelocity = 300;

////////////////////////////////////////////////////////////////////////////////
// Load object (should call base class version!)
////////////////////////////////////////////////////////////////////////////////
int16_t CFireball::Load(										// Returns 0 if successfull, non-zero otherwise
	RFile* pFile,											// In:  File to load from
	bool bEditMode,										// In:  True for edit mode, false otherwise
	int16_t sFileCount,										// In:  File count (unique per file, never 0)
	uint32_t	ulFileVersion)									// In:  Version of file format to load.
	{
	int16_t sResult = CThing::Load(pFile, bEditMode, sFileCount, ulFileVersion);

	if (sResult == 0)
	{
		// Load common data just once per file (not with each object)
		if (ms_sFileCount != sFileCount)
		{
			ms_sFileCount = sFileCount;

			// Load static data.
			switch (ulFileVersion)
			{
				default:
				case 1:
					break;
			}
		}

		// Load instance data.
		switch (ulFileVersion)
		{
			default:
			case 1:
				break;
		}

		// Make sure there were no file errors or format errors . . .
		if (!pFile->Error() && sResult == 0)
		{
			// Get resources
			sResult = GetResources();
		}
		else
		{
			sResult = -1;
			TRACE("CFireball::Load(): Error reading from file!\n");
		}
	}
	else
	{
		TRACE("CFireball::Load():  CThing::Load() failed.\n");
	}

	return sResult;
}

////////////////////////////////////////////////////////////////////////////////
// Startup object
////////////////////////////////////////////////////////////////////////////////
int16_t CFireball::Startup(void)								// Returns 0 if successfull, non-zero otherwise
{
	return Init();
}

////////////////////////////////////////////////////////////////////////////////
// Shutdown object
////////////////////////////////////////////////////////////////////////////////
int16_t CFireball::Shutdown(void)							// Returns 0 if successfull, non-zero otherwise
{
	return 0;
}

////////////////////////////////////////////////////////////////////////////////
// Suspend object
////////////////////////////////////////////////////////////////////////////////
void CFireball::Suspend(void)
{
	m_sSuspend++;
}

////////////////////////////////////////////////////////////////////////////////
// Resume object
////////////////////////////////////////////////////////////////////////////////
void CFireball::Resume(void)
{
	m_sSuspend--;

	// If we're actually going to start updating again, we need to reset
	// the time so as to ignore any time that passed while we were suspended.
	// This method is far from precise, but I'm hoping it's good enough.
	if (m_sSuspend == 0)
		m_lPrevTime = m_pRealm->m_time.GetGameTime();
}


////////////////////////////////////////////////////////////////////////////////
// Update object
////////////////////////////////////////////////////////////////////////////////
void CFireball::Update(void)
{
	int32_t lThisTime;
	double dSeconds;
	double dDistance;
	double dNewX;
	double dNewZ;

	if (!m_sSuspend)
	{
		lThisTime = m_pRealm->m_time.GetGameTime();
		m_lAnimTime += lThisTime - m_lPrevTime;

		// See if we killed ourselves
		if (ProcessFireballMessages() == State_Deleted)
			{
			delete this;
			return;
			}

		switch (m_eState)
		{
			case State_Fire:
				if (lThisTime < m_lTimeToLive)
				{
					if (m_bMoving)
					{
						// Update position using wind direction and velocity
						dSeconds = ((double) lThisTime - (double) m_lPrevTime) / 1000.0;
						// Apply internal velocity.
						dDistance	= m_dHorizVel * dSeconds;
						float cosp;
						float sinp;
						rspSinCos(m_dRot, &sinp, &cosp);
						dNewX	= m_dX + cosp * dDistance;
						dNewZ	= m_dZ - sinp * dDistance;

						// Check attribute map for walls, and if you hit a wall, 
						// set the timer so you will die off next time around.
						int16_t sHeight = m_pRealm->GetHeight((int16_t) dNewX, (int16_t) dNewZ);
						// If it hits a wall taller than itself, then it will rotate in the
						// predetermined direction until it is free to move.
						if ((int16_t) m_dY < sHeight ||
							!m_pRealm->IsPathClear(	// Returns true, if the entire path is clear.                 
															// Returns false, if only a portion of the path is clear.     
															// (see *psX, *psY, *psZ).                                    
							(int16_t) m_dX, 				// In:  Starting X.                                           
							(int16_t) m_dY, 				// In:  Starting Y.                                           
							(int16_t) m_dZ, 				// In:  Starting Z.                                           
							3.0, 							// In:  Rate at which to scan ('crawl') path in pixels per    
															// iteration.                                                 
															// NOTE: Values less than 1.0 are inefficient.                
															// NOTE: We scan terrain using GetHeight()                    
															// at only one pixel.                                         
															// NOTE: We could change this to a speed in pixels per second 
															// where we'd assume a certain frame rate.                    
							(int16_t) dNewX, 			// In:  Destination X.                                        
							(int16_t) dNewZ,				// In:  Destination Z.                                        
							0,								// In:  Max traverser can step up.                      
							NULL,							// Out: If not NULL, last clear point on path.                
							NULL,							// Out: If not NULL, last clear point on path.                
							NULL,							// Out: If not NULL, last clear point on path.                
							false) 						// In:  If true, will consider the edge of the realm a path
															// inhibitor.  If false, reaching the edge of the realm    
															// indicates a clear path.                                 
						)
						{
							// Stop moving and fix yourself on a random spot on the wall.
							m_bMoving = false;				
							m_dX += (-3 + GetRand() % 7);
							m_dY += (-3 + GetRand() % 7);
							m_dZ += (-3 + GetRand() % 7);

							// Update sphere
							m_smash.m_sphere.sphere.X = m_dX;
							m_smash.m_sphere.sphere.Y = m_dY;
							m_smash.m_sphere.sphere.Z = m_dZ;
						}
						else
						{
							m_dX = dNewX;
							m_dZ = dNewZ;

							// Update sphere
							m_smash.m_sphere.sphere.X = m_dX;
							m_smash.m_sphere.sphere.Y = m_dY;
							m_smash.m_sphere.sphere.Z = m_dZ;

							// Check for collisions
							CSmash* pSmashed = NULL;
							GameMessage msg;
							msg.msg_Burn.eType = typeBurn;
							msg.msg_Burn.sPriority = 0;
							msg.msg_Burn.sDamage = 10;
							msg.msg_Burn.u16ShooterID = m_u16ShooterID;
							m_pRealm->m_smashatorium.QuickCheckReset(&m_smash, m_u32CollideIncludeBits,
																				  m_u32CollideDontcareBits, 
																				  m_u32CollideExcludeBits);
							while (m_pRealm->m_smashatorium.QuickCheckNext(&pSmashed))
								if (pSmashed->m_pThing->GetInstanceID() != m_u16ShooterID)
									SendThingMessage(&msg, pSmashed->m_pThing);				
						}
					}

				}
				else
				{
					delete this;
					return;
				}
				break;

			default:
				break;
		}
		m_lPrevTime = lThisTime;
	}
}


////////////////////////////////////////////////////////////////////////////////
// Render object
////////////////////////////////////////////////////////////////////////////////
void CFireball::Render(void)
{
	
	CAlphaAnim* pAnim;

	pAnim = (CAlphaAnim*) m_pAnimChannel->GetAtTime(m_lAnimTime % m_pAnimChannel->TotalTime());

	if (pAnim) // && m_sCurrentAlphaChannel >= 0)
	{
		// No special flags
		m_sprite.m_sInFlags = 0; 

		// Map from 3d to 2d coords
		Map3Dto2D(m_dX, m_dY, m_dZ, &(m_sprite.m_sX2), &(m_sprite.m_sY2) );
		// Offset by animations 2D offsets.
		m_sprite.m_sX2	+= pAnim->m_sX;
		m_sprite.m_sY2	+= pAnim->m_sY;

		// Priority is based on our Z position.
		m_sprite.m_sPriority = m_dZ;

		// Layer should be based on info we get from attribute map.
		m_sprite.m_sLayer = CRealm::GetLayerViaAttrib(m_pRealm->GetLayer((int16_t) m_dX, (int16_t) m_dZ));

//		m_sprite.m_sAlphaLevel = 200;
		if (m_lTotalFlameTime == 0)
			{
			// Safety.
			m_lTotalFlameTime = 500;
			}

//		m_sprite.m_sAlphaLevel = MIN((long)255, (long) (((m_lTimeToLive - m_pRealm->m_time.GetGameTime()) / m_lTotalFlameTime) * 255));

		// Copy the color info and the alpha channel to the Alpha Sprite
		
		// todo?
		//m_sprite.m_pImage = &(pAnim->m_imColor);

		// todo: clean up		
		if ( m_pAnimChannel->id == 7)
		{								
			m_sprite.texture = m_pTinyFireAan;
			m_sprite.tileId = pAnim->tileId;
			m_sprite.startU = pAnim->startU;
			m_sprite.startV = pAnim->startV;
			m_sprite.endU = pAnim->endU;
			m_sprite.endV = pAnim->endV;
			m_sprite.imageWidth = pAnim->width;
			m_sprite.imageHeight = pAnim->height;									
		}
		
		// Now there is only 1 alpha mask.
		m_sCurrentAlphaChannel = 0; //MIN(m_sCurrentAlphaChannel, (short) (m_sTotalAlphaChannels - 1));
	
		m_sprite.hasAlphaChannel = true;
		// Adjust level between 0 and max so it gets more opaque with time.
		m_sprite.m_sAlphaLevel = MIN_ALPHA + MAX_ALPHA - (MAX_ALPHA * (m_lTimeToLive - m_pRealm->m_time.GetGameTime()) ) / m_lTotalFlameTime ;

		// todo: clean up
		if (m_sprite.m_sAlphaLevel == 255)
		{
			m_sprite.m_sAlphaLevel = 254;
		}
	
		// Keep in range.
		if (m_sprite.m_sAlphaLevel < 0)
			m_sprite.m_sAlphaLevel = 0;
		else if (m_sprite.m_sAlphaLevel > MAX_ALPHA)
			m_sprite.m_sAlphaLevel = MAX_ALPHA;

		// Update sprite in scene
		m_pRealm->m_scene.UpdateSprite(&m_sprite);
		
	}
}

////////////////////////////////////////////////////////////////////////////////
// Setup
////////////////////////////////////////////////////////////////////////////////

int16_t CFireball::Setup(									// Returns 0 if successfull, non-zero otherwise
	int16_t sX,												// In:  New x coord
	int16_t sY,												// In:  New y coord
	int16_t sZ,												// In:  New z coord
	int16_t sDir,												// In:  Direction of travel
	int32_t lTimeToLive,										// In:  Number of milliseconds to burn, default 1sec
	U16 u16ShooterID)										// In:  Shooter's ID so you don't hit him
{
	int16_t sResult = 0;
	
	// Use specified position
	m_dX = (double)sX;
	m_dY = (double)sY;
	m_dZ = (double)sZ;
	m_dRot = sDir;
	m_lPrevTime = m_pRealm->m_time.GetGameTime();
	m_lCollisionTimer = m_lPrevTime + ms_lCollisionTime;
	m_u16ShooterID = u16ShooterID;
	m_lAnimTime = GetRandom();
	m_dHorizVel	= ms_dFireVelocity; 

	m_lTotalFlameTime = lTimeToLive;
	m_lTimeToLive = m_pRealm->m_time.GetGameTime() + lTimeToLive;
	m_sCurrentAlphaChannel = 0;
	
	// Load resources
	sResult = GetResources();

	if (sResult == SUCCESS)
		sResult = Init();

	return sResult;

}

////////////////////////////////////////////////////////////////////////////////
// Init
////////////////////////////////////////////////////////////////////////////////

int16_t CFireball::Init(void)
{
	int16_t sResult = SUCCESS;
	CAlphaAnim* pAnim = NULL;

	m_pTinyFireAan = g_pvrRenderer->m_TextureRepository.Get(PVR_TEXTURE_FIRE_AAN);

	// Update sphere
	m_smash.m_sphere.sphere.X = m_dX;
	m_smash.m_sphere.sphere.Y = m_dY;
	m_smash.m_sphere.sphere.Z = m_dZ;
	m_smash.m_sphere.sphere.lRadius = ms_sSmallRadius;
	m_smash.m_bits = CSmash::Fire;
	m_smash.m_pThing = this;
	// Update the smash
	m_pRealm->m_smashatorium.Update(&m_smash);
	m_eState = CWeapon::State_Idle;

	// Set the collision bits
	m_u32CollideIncludeBits = CSmash::Character | CSmash::Barrel | CSmash::Mine | CSmash::Misc;
	m_u32CollideDontcareBits = CSmash::Good | CSmash::Bad;
	m_u32CollideExcludeBits = 0;

	return sResult;
}

////////////////////////////////////////////////////////////////////////////////
// Get all required resources
////////////////////////////////////////////////////////////////////////////////
int16_t CFireball::GetResources(void)			// Returns 0 if successfull, non-zero otherwise
{
	int16_t sResult = SUCCESS;

	sResult = rspGetResource(&g_resmgrGame, m_pRealm->Make2dResPath(SMALL_FILE), &m_pAnimChannel, RFile::LittleEndian);
	
	if (sResult != 0)
		TRACE("CFireball::GetResources - Error getting fire animation resource\n");

			// todo: clean up			
			m_pAnimChannel->id = 7;			
			for (int i = 0; i < m_pAnimChannel->NumItems(); i++ )
			{				
				CAlphaAnim* tmp = (CAlphaAnim*) m_pAnimChannel->GetItem(i);
									
				tmp->tileId = 1;
				
				switch (i)
				{
					// Frame 1
					//   18 25
					case 0:					
					tmp->startU = 0.015625f;
					tmp->startV =  0.90234375f;
					tmp->endU =  0.0859375;
					tmp->endV = 1.0;
					tmp->width = 18;
					tmp->height = 25;					
					break;



					// Frame 2
					//    1.0 16 25
					case 1:					
					tmp->startU = 0.0859375;
					tmp->startV = 0.90234375;
					tmp->endU = 0.1484375;
					tmp->endV = 1.f;
					tmp->width = 16;
					tmp->height = 25;					
					break;

					// Frame 3
				// 0.1484375 0.90625 0.21875 1.0 18 24
					case 2:					
					tmp->startU = 0.148f;
					tmp->startV = 0.90625f;
					tmp->endU = 0.218f;
					tmp->endV = 1.f;
					tmp->width = 18;
					tmp->height = 24;					
					break;


					// Frame 4
				// 0.21875 0.93359375 0.28125 1.0 16 17
					case 3:					
					tmp->startU = 0.218f;
					tmp->startV = 0.93359375f;
					tmp->endU = 0.281f;
					tmp->endV = 1.f;
					tmp->width = 16;
					tmp->height = 17;					
					break;

					// Frame 5
				//  0.28125 0.921875 0.33984375 1.0 15 20
					case 4:					
					tmp->startU = 0.281f;
					tmp->startV = 0.921875f;
					tmp->endU = 0.339f;
					tmp->endV = 1.f;
					tmp->width = 15;
					tmp->height = 20;					
					break;


					// Frame 6
		// 0.33984375 0.9140625 0.40234375 1.0 16 22
					case 5:					
					tmp->startU = 0.339f;
					tmp->startV = 0.91406250f;
					tmp->endU = 0.402f;
					tmp->endV = 1.f;
					tmp->width = 16;
					tmp->height = 22;					
					break;


					// Frame 7
				//  0.40234375 0.91796875 0.48828125 1.0 22 21
					case 6:					
					tmp->startU = 0.402f;
					tmp->startV = 0.91796875f;
					tmp->endU = 0.488f;
					tmp->endV = 1.f;
					tmp->width = 22;
					tmp->height = 21;					
					break;


					// Frame 8
				// 0.4921875 0.93359375 0.5625 1.0 18 17
					case 7:					
					tmp->startU = 0.488f;
					tmp->startV = 0.93359375f;
					tmp->endU = 0.558f;
					tmp->endV = 1.f;
					tmp->width = 18;
					tmp->height = 17;					
					break;


					// Frame 9
					//  0.5625 0.90234375 0.63671875 1.0 19 25
					case 8:					
					tmp->startU = 0.558f;
					tmp->startV = 0.90234375f;
					tmp->endU = 0.632f;
					tmp->endV = 1.f;
					tmp->width = 19;
					tmp->height = 25;					
					break;


					// Frame 10
			// 0.63671875 0.9296875 0.71484375 1.0 20 18
					case 9:					
					tmp->startU = 0.632f;
					tmp->startV = 0.9296875f;
					tmp->endU = 0.710f;
					tmp->endV = 1.f;
					tmp->width = 20;
					tmp->height = 18;					
					break;


					// Frame 11
				// 18 16
					case 10:					
					tmp->startU = 0.71484375f;
					tmp->startV = 0.9375f;
					tmp->endU = 0.78515625f;
					tmp->endV = 1.f;
					tmp->width = 18;
					tmp->height = 16;					
					break;

					// Frame 12
		//    20 23
					case 11:					
					tmp->startU =0.78515625f;
					tmp->startV =0.91015625f;
					tmp->endU = 0.86328125f;
					tmp->endV = 1.f;
					tmp->width = 20;
					tmp->height = 23;					
					break;


					// Frame 13
				//  0.92578125 0.92578125 1.0 16 19
					case 12:					
					tmp->startU = 0.86328125f;
					tmp->startV = .92578125f;
					tmp->endU =  0.92578125f;
					tmp->endV = 1.f;
					tmp->width = 16;
					tmp->height = 19;					
					break;


					// Frame 14
					// 1.0 16 14
					case 13:					
					tmp->startU = 0.92578125f;
					tmp->startV = 0.9453125f;
					tmp->endU =  0.98828125f;
					tmp->endV = 1.f;
					tmp->width = 16;
					tmp->height = 14;					
					break;

				}												
			}
		
	return sResult;
}


////////////////////////////////////////////////////////////////////////////////
// Free all resources
////////////////////////////////////////////////////////////////////////////////
int16_t CFireball::FreeResources(void)						// Returns 0 if successfull, non-zero otherwise
{
	int16_t sResult = 0;
	bool purge = m_pRealm->m_flags.bPurgeResOnShutdown;
	purge = false;
	if (purge)
	{
		rspReleaseAndPurgeResource(&g_resmgrGame, &m_pAnimChannel);
	}
	else
	{
		rspReleaseResource(&g_resmgrGame, &m_pAnimChannel);
	}	

	return sResult;
}

////////////////////////////////////////////////////////////////////////////////
// Preload - basically trick the resource manager into caching resources for fire
//				 animations before play begins so that when a fire is set for
//				 the first time, there won't be a delay while it loads.
////////////////////////////////////////////////////////////////////////////////

int16_t CFireball::Preload(
	CRealm* prealm)				// In:  Calling realm.
{	
	int16_t sResult;
	ChannelAA* pRes;
	sResult = rspGetResource(&g_resmgrGame, prealm->Make2dResPath(SMALL_FILE), &pRes, RFile::LittleEndian);
	rspReleaseResource(&g_resmgrGame, &pRes);
	return sResult;
}


int16_t CFireball::Unload(
	CRealm* prealm)				// In:  Calling realm.
{	
	return 0;	
}



////////////////////////////////////////////////////////////////////////////////
// ProcessFireballMessages
////////////////////////////////////////////////////////////////////////////////

CFireball::CFireballState CFireball::ProcessFireballMessages(void)
{
	CFireballState eNewState = State_Idle;

	GameMessage msg;

	if (m_MessageQueue.DeQ(&msg) == true)
	{
		switch(msg.msg_Generic.eType)
		{
			case typeObjectDelete:
				m_MessageQueue.Empty();
				return State_Deleted;
				break;
		}
	}
	// Dump the rest of the messages
	m_MessageQueue.Empty();

	return eNewState;
}

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