////////////////////////////////////////////////////////////////////////////////
//
// 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
//
// fire.cpp
// Project: Postal
//
// This module implements the CFire weapon class which is a burning flame
//	for several different effects and weapons.
// 
//
// History:
//		01/17/97 BRH	Started this weapon object.
//
//		01/23/97 BRH	Updated the time to GetGameTime rather than using
//							real time..
//
//		02/04/97	JMI	Changed LoadDib() call to Load() (which now supports
//							loading of DIBs).
//
//		02/06/97 BRH	Added RAnimSprite animation of the explosion for now.  
//							We are going to do an Alpha effect on the explosion, so
//							there are two animations, one of the image and one of
//							the Alpha information stored as a BMP8 animation.  When
//							the Alpha effect is ready, we will pass a frame from
//							each animation to a function to draw it.
//
//		02/06/97 BRH	Fixed problem with timer.  Since all Explosion objects
//							are using the same resource managed animation, they cannot
//							use the animation timer, they have to do the timing 
//							themselves.
//
//		02/07/97 BRH	Changed the sprite from CSprite2 to CSpriteAlpha2 for
//							the Alpha Blit effect.
//
//		02/09/97 BRH	Started the Fire from Explode file since they are
//							similar.
//
//		02/10/97	JMI	rspReleaseResource() now takes a ptr to a ptr.
//
//		02/11/97 BRH	Changed the fire to start on a random frame number
//							so if you have many fires, they don't pulsate or all
//							burn in sync with each other.
//
//		02/14/97 BRH	Changed from using the RAnimSprite to channel data.
//
//		02/17/97 BRH	Now uses the resource manager to get the assets and starts
//							at a random time interval so the fire will be random again.
//
//		02/17/97 BRH	Changed the lifetime to be time based rather than frame
//							based which was causing the fire to live on forever 
//							since being switched from RAnimSprite to RChannel1.
//
//		02/18/97 BRH	Now the fire changes to different Alpha channels as it
//							burns out during its time to live.  
//
//		02/19/97 BRH	Checks for collisions and sends messages.
//
//		02/19/97 BRH	Added the ability to run both small and large fire
//							animations.  Change the duration on the alpha layers
//							so that the initial alpha channel gets played for 80%
//							of the burning time.  Also added bThick parameter to startup
//							which will start using the 0th Alpha channel which is
//							more opaque.  If you want more Alpha, set to false which
//							will start on the next Alpha level down.
//
//		02/23/97 BRH	Added static Preload() funciton which will be called
//							before play begins to cache a resource for this object.
//
//		02/24/97	JMI	No longer sets the m_type member of the m_sprite b/c it
//							is set by m_sprite's constructor.
//
//		02/24/97 BRH	Set the default state in ProcessMessages
//
//		02/24/97 BRH	Added a timer for checkin collisions so it doesn't have
//							to check each time, but it was checking only when changing
//							alpha levels which was too long.
//
//		03/05/97	JMI	Render()'s mapping from 3D to 2D had a typo (was adding m_dY
//							instead of subtracting).  Now uses Map3Dto2D().
//
//		03/13/97	JMI	Load now takes a version number.
//
//		04/10/97 BRH	Updated this to work with the new multi layer attribute
//							maps.
//
//		04/14/97 BRH	Added CSmash::Item to the collide bits so that the fire
//							will send messages to barrels and other items.
//
//		04/21/97 BRH	Added Smoke animation to the fire and the ability of the
//							fire to change to smoke.
//
//		02/22/97 BRH	Adjusted the timer for the smoke effect to eliminate some
//							of the final frames so that the smoke wouldn't pulsate
//							like it did.				
//
//		04/23/97	JMI	Changed this item's m_smash bits from CSmash::Item to
//							CSmash::Fire.
//							Now affects Characters, Miscs, Mines, and Barrels.
//
//		04/24/97 BRH	Added static wind direction variable that will get
//							adjusted slightly by each new creation of smoke which
//							calls WindDirectionUpdate() to randomly vary the wind
//							direction.
//
//		04/25/97 BRH	Fixed problem with smoke that was created as smoke, 
//							setting people on fire.  Also fixed wall detection
//							and added an individual direction variable to each 
//							instance of smoke that initially copies the wind
//							direction and uses it until it hits a wall, then it
//							rotates in one direction or the other until it is
//							free to move again.
//
//		05/09/97	JMI	Update() now moves the smashatorium object when the CFire
//							is not Smoke.
//
//		05/29/97	JMI	Removed ASSERT on m_pRealm->m_pAttribMap which no longer
//							exists.
//
//		06/11/97 BRH	Pass along the m_u16ShooterID value in the Burn message.
//
//		06/15/97 BRH	Fixed Smoke going past animation by 1 frame.
//
//		06/16/97 BRH	Fixed smoke init of static wind direction.  Now it 
//							inits the wind direction on class load so that it doesn't
//							cause problems for the demo mode.
//
//		06/17/97 MJR	Same as previous one for wind velocity.
//
//					MJR	Moved resetting of statics to Preload(), since in most
//							cases, fire or smoke are not Load()'ed.
//
//		06/18/97 BRH	Changed over to using GetRandom()
//
//		06/26/97 BRH	Added CSmash::AlmostDead to the include bits for fire so
//							that writhing guys can be killed by fire.
//
//		07/01/97 BRH	Added small smoke animation.
//
//		07/04/97 BRH	Added an auto alpha blend on the small smoke for the
//							rocket trails so they can blend into alpha based on
//							their time to live.  May need to disable the
//							alpha channel for it to work correctly.
//
//		07/08/97	JMI	Fixed Render() to distribute the homogeneous alpha level
//							better.  Still needs tuning.
//
//		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/13/97 BRH	Changed the animations to use only 1 alpha mask and change
//							the alpha level based on time.
//
//		07/20/97	JMI	Added some ASSERTs.
//
//		07/23/97 BRH	Changed small fires to create small smokes rather than 
//							large which slows down the game quite a bit.
//
//		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.
//
//		08/11/97 BRH	If alpha blending is turned off, as a performance option,
//							then don't even blit the smoke since without the alpha
//							effect, you can't see through it at all.
//
//		08/20/97	JMI	Now does a range check on m_sCurrentAlphaLevel after
//							decrementing.
//
//		09/02/97	JMI	Added m_u16FireStarterID.  This is used for a special case
//							when the starter of the fire is not the thing using the
//							fire as a weapon (e.g., when a guy catches fire he can
//							use the fire on other people by running into them causing
//							them to catch on fire; however, if his own fire kills him
//							it is to the creator of the fire's credit that he dies).
//
////////////////////////////////////////////////////////////////////////////////
#define FIRE_CPP

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

#include "fire.h"
#include "dude.h"
#include "game.h"
#include "reality.h"

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

#define AA_FILE				"fire.aan"
#define LARGE_FILE			"fire.aan"
#define SMALL_FILE			"smallfire.aan"
#define SMOKE_FILE			"smoke.aan"
#define SMALL_SMOKE_FILE	"tinysmoke.aan"

#define INIT_WIND_DIR		30
#define INIT_WIND_VEL		30

#define MAX_ALPHA				200	// Used for smoke trails

#define THICK_ALPHA			255	// Start alpha level for thick fire
#define THIN_ALPHA			200	// Start alpha level for thin fire
#define DIEDOWN_ALPHA		100	// Point at which it looks like its dying down
#define SMOLDER_ALPHA		30		// Point at which it is too weak to burn anyone

#define BRIGHT_PERCENT		0.80	// Amount of the time it should be more opaque

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

// Let this auto-init to 0
int16_t CFire::ms_sFileCount;
int16_t CFire::ms_sLargeRadius = 20;
int16_t CFire::ms_sSmallRadius = 8;
int16_t CFire::ms_sWindDirection = INIT_WIND_DIR;		// Start wind in this direction
int32_t  CFire::ms_lCollisionTime = 250;					// Check for collisions this often
int32_t  CFire::ms_lSmokeTime = 10000;						// Time to let smoke run
double CFire::ms_dWindVelocity = INIT_WIND_VEL;		// Pixels per second drift due to wind


////////////////////////////////////////////////////////////////////////////////
// Load object (should call base class version!)
////////////////////////////////////////////////////////////////////////////////
int16_t CFire::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;

			// Init the static wind direction and velocity when the class loads.
			ms_sWindDirection = INIT_WIND_DIR;
			ms_dWindVelocity = INIT_WIND_VEL;

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

		// Load instance data.
		switch (ulFileVersion)
		{
			default:
			case 1:
				pFile->Read(&m_eFireAnim);
				break;
		}

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

	return sResult;
}


////////////////////////////////////////////////////////////////////////////////
// Save object (should call base class version!)
////////////////////////////////////////////////////////////////////////////////
int16_t CFire::Save(										// Returns 0 if successfull, non-zero otherwise
	RFile* pFile,											// In:  File to save to
	int16_t sFileCount)										// In:  File count (unique per file, never 0)
{
	int16_t	sResult	= CThing::Save(pFile, sFileCount);
	if (sResult == 0)
	{
		// Save common data just once per file (not with each object)
		if (ms_sFileCount != sFileCount)
		{
			ms_sFileCount = sFileCount;

			// Save static data
		}

		pFile->Write(&m_eFireAnim);
	}
	else
	{
		TRACE("CFire::Save(): CThing::Save() failed.\n");
	}

	return sResult;
}


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

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

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

////////////////////////////////////////////////////////////////////////////////
// Resume object
////////////////////////////////////////////////////////////////////////////////
void CFire::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 CFire::Update(void)
{
	int32_t lThisTime;
	double dSeconds;
	double dDistance;
	double dNewX;
	double dNewZ;

	if (!m_sSuspend)
	{
		// See if we killed ourselves
		if (ProcessMessages() == State_Deleted)
			return;

		if (m_lTimer < m_lBurnUntil)
		{
			lThisTime = m_pRealm->m_time.GetGameTime();
			m_lTimer += lThisTime - m_lPrevTime;
			// See if its time to change to the next alpha channel
			if (m_lTimer > m_lCurrentAlphaTimeout)
			{
				m_sCurrentAlphaLevel--;
				// Range check.
				if (m_sCurrentAlphaLevel < 0)
					m_sCurrentAlphaLevel	= 0;
				else if (m_sCurrentAlphaLevel > 255)
					m_sCurrentAlphaLevel	= 255;

				if (m_lTimer < m_lAlphaBreakPoint)
					m_lCurrentAlphaTimeout += m_lBrightAlphaInterval;
				else
					m_lCurrentAlphaTimeout += m_lDimAlphaInterval;
			}

			if (lThisTime > m_lCollisionTimer)
			{
				// If the fire is not smoldering out, then it has the ability
				// to set other things on fire and should check collisions
				// to see which things it should tell to burn.
				if (m_bSendMessages && m_sCurrentAlphaLevel > SMOLDER_ALPHA && m_eFireAnim != Smoke && m_eFireAnim != SmallSmoke)
				{
					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))
						{
						// Default to the standard case where credit is given to the
						// shooter.
						msg.msg_Burn.u16ShooterID	= m_u16ShooterID;

						if ((m_bIsBurningDude) && (pSmashed->m_pThing->GetClassID() != CDudeID))
							UnlockAchievement(ACHIEVEMENT_TOUCH_SOMEONE_WHILE_BURNING);

						// If the fire starter ID is set . . .
						if (m_u16FireStarterID != CIdBank::IdNil)
							{
							// If this is the shooter . . .
							if (pSmashed->m_pThing->GetInstanceID() == m_u16ShooterID)
								{
								// The shooter is damaged by his own fire with credit
								// given to the fire starter.
								msg.msg_Burn.u16ShooterID	= m_u16FireStarterID;
								}
							}

						// Burn.
						SendThingMessage(&msg, pSmashed->m_pThing);				
						}
				}
				// Reset collision timer for next time
				m_lCollisionTimer = lThisTime + ms_lCollisionTime;
			}

			// If this is smoke, make it drift in the wind direction
			if (m_eFireAnim == Smoke || m_eFireAnim == SmallSmoke)
			{
				// Update position using wind direction and velocity
				dSeconds = ((double) lThisTime - (double) m_lPrevTime) / 1000.0;
				// Apply internal velocity.
				dDistance	= ms_dWindVelocity * dSeconds;
				dNewX	= m_dX + COSQ[(int16_t) m_sRot] * dDistance;
				dNewZ	= m_dZ - SINQ[(int16_t) m_sRot] * 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)
				{
					if (m_bTurnRight)
						m_sRot = rspMod360(m_sRot - 20);
					else
						m_sRot = rspMod360(m_sRot + 20);
				}
				else
				// else it is ok, so update its new position
				{
					m_dX = dNewX;
					m_dZ = dNewZ;
				}
			}
			else
			{
				// Update our smashatorium location.
				m_smash.m_sphere.sphere.X = m_dX;
				m_smash.m_sphere.sphere.Y = m_dY;
				m_smash.m_sphere.sphere.Z = m_dZ;
				// Update the smash.
				m_pRealm->m_smashatorium.Update(&m_smash);
			}

			m_lPrevTime = lThisTime;
		}
		else
		{
			// If its done smoking, then delete it
			if (m_eFireAnim == Smoke || m_eFireAnim == SmallSmoke)
			{
				delete this;
			}
			// Else change the fire to smoke
			else
			{
				if (Smokeout() != SUCCESS)
					delete this;
			}
		}
	}
}


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

	if (m_eFireAnim == Smoke || m_eFireAnim == SmallSmoke)	
	{
		pAnim = (CAlphaAnim*) m_pAnimChannel->GetAtTime(m_lTimer); 
		// For a performance gain, don't blit the smoke at all if alpha
		// blending is turned off - its impossible to see through when
		// alpha blending is off anyway.
		if (g_GameSettings.m_sAlphaBlend)
			m_sprite.m_sInFlags = 0;
		else
			m_sprite.m_sInFlags = CSprite::InHidden;
	}
	else
	{
		pAnim = (CAlphaAnim*) m_pAnimChannel->GetAtTime(m_lTimer % m_pAnimChannel->TotalTime());
	}	

	if (pAnim)
	{
		// 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));

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

		// If its the tiny smoke (for trails) 
		// Do the alpha based on the time to live.
		if (m_eFireAnim == SmallSmoke)
		{
			// Set the alpha level so it gets more translucent over time.
			m_sprite.m_sAlphaLevel = MAX_ALPHA - (MAX_ALPHA * (m_lTimer - m_lStartTime) ) / m_lTimeToLive ;
			
			// Do a range check.
			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;
		}
		else
		{
			m_sprite.m_sAlphaLevel = m_sCurrentAlphaLevel;
		}

		// Now there is only one alpha mask
		m_sprite.m_pimAlpha = &(pAnim->m_pimAlphaArray[0]);

		ASSERT(m_sprite.m_sAlphaLevel <= 255);
		ASSERT(m_sprite.m_sAlphaLevel >= 0);

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

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

int16_t CFire::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
	int32_t lTimeToLive,										// In:  Number of milliseconds to burn, default 1sec
	bool  bThick,											// In:  Use thick fire (more opaque) default = true
	FireAnim eAnimType)									// In:  Animation type to use default = LargeFire
{
	int16_t sResult = 0;
	
	// Use specified position
	m_dX = (double)sX;
	m_dY = (double)sY;
	m_dZ = (double)sZ;
	m_lPrevTime = m_pRealm->m_time.GetGameTime();
	m_lCollisionTimer = m_lPrevTime + ms_lCollisionTime;

	m_eFireAnim = eAnimType;
	m_lTimeToLive = lTimeToLive;
	if (bThick)
		m_sCurrentAlphaLevel = THICK_ALPHA;
	else
		m_sCurrentAlphaLevel = THIN_ALPHA;
	
	// Load resources
	sResult = GetResources();

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

	m_sRot = ms_sWindDirection;
	m_bTurnRight = (GetRandom() & 0x01);

	return sResult;

}

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

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

	if (m_pAnimChannel != NULL)
	{
		m_lTimer = GetRandom() % m_pAnimChannel->TotalTime();
		m_lStartTime = m_lTimer;
		m_lBurnUntil = m_lTimer + m_lTimeToLive;
		m_lAlphaBreakPoint = m_lTimer + (m_lTimeToLive * BRIGHT_PERCENT);
		pAnim = (CAlphaAnim*) m_pAnimChannel->GetAtTime(0);
		ASSERT(pAnim != NULL);
		m_lBrightAlphaInterval = (m_lTimeToLive * BRIGHT_PERCENT) / MAX(1, m_sCurrentAlphaLevel - DIEDOWN_ALPHA);
		m_lDimAlphaInterval = (m_lTimeToLive * (100.0 - BRIGHT_PERCENT)) / MAX(1, DIEDOWN_ALPHA);
		m_lCurrentAlphaTimeout = m_lTimer + m_lBrightAlphaInterval;
		m_sTotalAlphaChannels = 1;
	}

	switch (m_eFireAnim)
	{
		case LargeFire:
			// 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_sLargeRadius;
			m_smash.m_bits = CSmash::Fire;
			m_smash.m_pThing = this;
			// Update the smash
			m_pRealm->m_smashatorium.Update(&m_smash);
			break;

		case SmallFire:
			// 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);
			break;

		case Smoke:
			m_smash.m_pThing = NULL;			
			m_bSendMessages = false;
			break;

		case SmallSmoke:
			m_smash.m_pThing = NULL;			
			m_bSendMessages = false;
			m_lStartTime = m_lTimer = GetRandom() % m_pAnimChannel->TotalTime() / 3;
			m_lBurnUntil = m_lTimer + m_lTimeToLive;
			break;			
	}

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

	return sResult;
}

////////////////////////////////////////////////////////////////////////////////
// Smokeout - Change from fire to smoke
////////////////////////////////////////////////////////////////////////////////

int16_t CFire::Smokeout(void)
{
	int16_t sResult = SUCCESS;

	// Modify the wind direction slightly
	WindDirectionUpdate();

	// Remove smash from the smashatorium if it was being used
	if (m_smash.m_pThing)
		m_pRealm->m_smashatorium.Remove(&m_smash);

	m_bSendMessages = false;

	// Release the fire animation and get the smoke animation
	FreeResources();
	if (m_eFireAnim == SmallFire)
		m_eFireAnim = SmallSmoke;
	else
		m_eFireAnim = Smoke;
	sResult = GetResources();

	// Reset timers
	CAlphaAnim* pAnim = NULL;

	if (m_pAnimChannel != NULL)
	{
		// Reset alpha level
		m_sCurrentAlphaLevel = THICK_ALPHA;
		pAnim = (CAlphaAnim*) m_pAnimChannel->GetAtTime(0);
		ASSERT(pAnim != NULL);
//		m_lTimeToLive = m_pAnimChannel->TotalTime();
		// use same time to live as the original
		m_lStartTime = m_lTimer = 0;
		m_lBurnUntil = m_lTimer + m_lTimeToLive;
		m_lAlphaBreakPoint = m_lTimer + (m_lTimeToLive * BRIGHT_PERCENT);
		m_lBrightAlphaInterval = (m_lTimeToLive * BRIGHT_PERCENT) / MAX(1, m_sCurrentAlphaLevel - DIEDOWN_ALPHA);
		m_lDimAlphaInterval = (m_lTimeToLive * (1.0 - BRIGHT_PERCENT)) / MAX(1, DIEDOWN_ALPHA);
		m_lCurrentAlphaTimeout = m_lTimer + m_lBrightAlphaInterval;
		m_sTotalAlphaChannels = 1;
	}

	return sResult;
}

////////////////////////////////////////////////////////////////////////////////
// Called by editor to init new object at specified position
////////////////////////////////////////////////////////////////////////////////
int16_t CFire::EditNew(									// 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 sResult = 0;
	
	// Use specified position
	m_dX = (double)sX;
	m_dY = (double)sY;
	m_dZ = (double)sZ;
	m_lTimer = GetRandom(); //m_pRealm->m_time.GetGameTime() + 1000;
	m_lPrevTime = m_pRealm->m_time.GetGameTime();

	// Load resources
	sResult = GetResources();

	return sResult;
}


////////////////////////////////////////////////////////////////////////////////
// Called by editor to modify object
////////////////////////////////////////////////////////////////////////////////
int16_t CFire::EditModify(void)
{
	return 0;
}


////////////////////////////////////////////////////////////////////////////////
// Called by editor to move object to specified position
////////////////////////////////////////////////////////////////////////////////
int16_t CFire::EditMove(									// 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
{
	m_dX = (double)sX;
	m_dY = (double)sY;
	m_dZ = (double)sZ;

	return 0;
}


////////////////////////////////////////////////////////////////////////////////
// Called by editor to update object
////////////////////////////////////////////////////////////////////////////////
void CFire::EditUpdate(void)
{
}


////////////////////////////////////////////////////////////////////////////////
// Called by editor to render object
////////////////////////////////////////////////////////////////////////////////
void CFire::EditRender(void)
{
	// In some cases, object's might need to do a special-case render in edit
	// mode because Startup() isn't called.  In this case it doesn't matter, so
	// we can call the normal Render().
	Render();
}


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

	switch (m_eFireAnim)
	{
		case LargeFire:
			sResult = rspGetResource(&g_resmgrGame, m_pRealm->Make2dResPath(LARGE_FILE), &m_pAnimChannel, RFile::LittleEndian);
			break;

		case SmallFire:
			sResult = rspGetResource(&g_resmgrGame, m_pRealm->Make2dResPath(SMALL_FILE), &m_pAnimChannel, RFile::LittleEndian);
			break;

		case Smoke:
			sResult = rspGetResource(&g_resmgrGame, m_pRealm->Make2dResPath(SMOKE_FILE), &m_pAnimChannel, RFile::LittleEndian);
			break;

		case SmallSmoke:
			sResult = rspGetResource(&g_resmgrGame, m_pRealm->Make2dResPath(SMALL_SMOKE_FILE), &m_pAnimChannel, RFile::LittleEndian);
			break;
	}

	if (sResult != 0)
		TRACE("CFire::GetResources - Error getting fire animation resource\n");

	return sResult;
}


////////////////////////////////////////////////////////////////////////////////
// Free all resources
////////////////////////////////////////////////////////////////////////////////
int16_t CFire::FreeResources(void)						// Returns 0 if successfull, non-zero otherwise
{
	int16_t sResult = 0;

	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 CFire::Preload(
	CRealm* prealm)				// In:  Calling realm.
{
	// Init the static wind direction and velocity when the class loads.
	ms_sWindDirection = INIT_WIND_DIR;
	ms_dWindVelocity = INIT_WIND_VEL;

	ChannelAA* pRes;
	int16_t sResult = rspGetResource(&g_resmgrGame, prealm->Make2dResPath(LARGE_FILE), &pRes, RFile::LittleEndian);
	rspReleaseResource(&g_resmgrGame, &pRes);
	sResult |= rspGetResource(&g_resmgrGame, prealm->Make2dResPath(SMALL_FILE), &pRes, RFile::LittleEndian);
	rspReleaseResource(&g_resmgrGame, &pRes);
	sResult |= rspGetResource(&g_resmgrGame, prealm->Make2dResPath(SMOKE_FILE), &pRes, RFile::LittleEndian);
	rspReleaseResource(&g_resmgrGame, &pRes);
	sResult |= rspGetResource(&g_resmgrGame, prealm->Make2dResPath(SMALL_SMOKE_FILE), &pRes, RFile::LittleEndian);
	rspReleaseResource(&g_resmgrGame, &pRes);
	return sResult;
}


////////////////////////////////////////////////////////////////////////////////
// ProcessMessages
////////////////////////////////////////////////////////////////////////////////

CFire::CFireState CFire::ProcessMessages(void)
{
	CFireState eNewState = State_Idle;

	GameMessage msg;

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

	return eNewState;
}

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