////////////////////////////////////////////////////////////////////////////////
//
// 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
//
// PowerUp.cpp
// Project: Nostril (aka Postal)
// 
// History:
//		05/08/97 JMI	Started.
//
//		05/09/97	JMI	Added m_smash and Init().
//
//		05/14/97	JMI	Added Grab() and Drop().
//							Also, Update() will not update collision sphere unless
//							there's no parent.
//							Also, increased COLLISION_RADIUS from 5 to 10.
//
//		05/29/97	JMI	Removed ASSERT on m_pRealm->m_pAttribMap which no longer
//							exists.
//
//		06/06/97	JMI	Now hotspot is bottom center of image (was center of 
//							image).
//							Increased COLLISION_RADIUS to 20 (was 10).
//							Got rid of the whole type thing.  Now uses a CStockPile
//							and can contain any combination of powerups w/i one    
//							instance.  Types are still used for loading old powerup
//							files.
//							Now EditModify() simply invokes m_stockpile.UserEdit().
//
//		06/09/97	JMI	Changed RES_NAME from "2d/box.bmp" to "2d/box.img".
//
//		06/10/97	JMI	Changed "First Aid Kit" to "Health".
//
//		06/12/97	JMI	Added support for new weapon members of CStockPile.
//
//		06/12/97	JMI	Added sHitPointMax parameter to GetDescription().
//
//		06/12/97	JMI	Drop was not checking that we had a parent before trying
//							to call RemoveChild() in the parent.
//
//		06/13/97	JMI	Moved priority spot to center of image vertically.
//
//		06/14/97	JMI	Decreased COLLISION_RADIUS to 10 (was 20).
//
//		06/14/97	JMI	Changed to a descendant of CItem3d instead of CThing.
//							Removed 2D bullshit.
//
//		06/14/97	JMI	Changed SCALE from 3.0 to 2.0.  Note that once Randy 
//							makes larger powerup specific animations, this should be
//							changed to 1.0.
//							Seems that the BoundingSphereToScreen() function does not
//							take scaling into account.
//
//		06/14/97	JMI	Added KevlarVest (CStockPile::m_sArmorLayers).
//
//		06/15/97	JMI	Added Backpack (CStockPile::m_sBackpack).
//							Now returns to normal spinning after blown up state is 
//							complete.
//
//		06/15/97	JMI	Reduced SCALE to 1.0 (was 2.0) and added some of the
//							real powerup filenames.
//
//		06/16/97	JMI	Added more real powerup names.
//
//					JMI	Added a user (audible) feedback function, PickUpFeedback().
//
//		06/17/97	JMI	Changed the 'no items' and 'multiple items' anim to
//							the mine crate.
//
//		06/25/97	JMI	Now calls PrepareShadow() in Init() which loads and sets up
//							a shadow sprite.
//
//		06/30/97 BRH	Caches the sample it uses in the Load function so that
//							the sound effect will be ready in any level that the
//							powerups are in.
//
//		07/15/97	JMI	Made GetDescription() and TypeToStockPile() static and 
//							added stockpiles as parms so they can be used more 
//							generically.
//							Also, added RepaginateNow().
//							Also, added IsEmpty().
//
//		07/15/97	JMI	Added some message handling functions.
//							Transferred powerup index enums into CStockPile.
//							Now boxes of multiple powerups explode into many when
//							blown up.
//
//		07/16/97	JMI	Now Setup() automatically calls Startup().
//							Moved IsEmpty() from powerup to stockpile.
//
//		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/23/97	JMI	Added separate launcher for napalm and icons for napalm
//							launcher, flame thrower, and fuel.
//
//		07/28/97	JMI	Changed "nplmlauncher" to "napalmer" for res name.
//
//		07/30/97	JMI	Added DeathWadLauncher to array of powerup names and
//							res names.
//
//		08/07/97	JMI	Added DoubleBarrel to array of powerup names and res 
//							names.
//
//		08/07/97	JMI	Added additional parameter to CAnim3D::Get() call.
//
//		08/07/97	JMI	Changed backpack resname from "3d/backpack" to
//							"3d/backpackicon".
//
//		08/17/97	JMI	Got rid of m_szMessages and all message related functions
//							and variables from CDude since we are now using the toolbar 
//							for dude status feedback to the user.  This includes:  
//							MsgTypeInfo, m_lNextStatusUpdateTime, m_lMsgUpdateDoneTime, 
//							m_print, m_bClearedStatus, m_szMessages[], m_sDeadMsgNum, 
//							ms_amtfMessages[], ms_u8FontForeIndex, ms_u8FontBackIndex,
//							ms_u8FontShadowIndex, DrawStatus(), StatusChange(), 
//							MessageChange(), Message(), UpdateFontColors(), 
//							CPowerUp::ms_apszPowerUpTypeNames[], 
//							CPowerUp::GetDescription(), and some strings and a string
//							array in localize.*.
//
//		08/28/97 BRH	Added a Preload function for the powerups since they
//							may pop up during gameplay, we want to have their assets
//							ready.
//
//////////////////////////////////////////////////////////////////////////////
//
// This CItem3d-derived class will represent power ups that the player can
// pick up.
//
//////////////////////////////////////////////////////////////////////////////
#define POWERUP_CPP

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

#include "PowerUp.h"
#include "dude.h"
#include "game.h"

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

#define GUI_FILE_NAME		"res/editor/PowerUp.gui"

#define GUI_ID_STOCKPILE	3

#define Y_AXIS_ROTATION_RATE	240.0

#define SCALE						1.0

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

// Animations.
char*	CPowerUp::ms_apszPowerUpResNames[CStockPile::NumStockPileItems + 2]	=
	{
	"3d/ammo",							// Bullets.
	"3d/grenadeicon",					// Grenades.
	"3d/cocktail",						// Cocktails.
	"3d/missileicon",					// Rockets.
	"3d/napalmicon",					// Napalm.
	"3d/shotshells",					// Shells.
	"3d/fuel",							// Fuel.
	"3d/minecrate",					// Mines.
	"3d/health",						// Health.
	"3d/gmissileicon",				// Heatseekers.

	"3d/machinegun",					//	MachineGun.
	"3d/launcher",						// MissileLauncher.
	"3d/shotgun",						// ShotGun.
	"3d/spraygun",						// SprayCannon.
	"3d/flmthrower",					// FlameThrower.
	"3d/napalmer",						// NapalmLauncher.
	"3d/napalmer",						// DeathWadLauncher.
	"3d/shotgun",						// DoubleBarrel.

	"3d/kevlarvest",					// KevlarVest.

	"3d/backpackicon",				// Backpack.

	// Insert new items above this item.
	"3d/minecrate",					// Multiple items.
	"3d/minecrate",					// No items.
	};

////////////////////////////////////////////////////////////////////////////////
// Load object (should call base class version!)
////////////////////////////////////////////////////////////////////////////////
int16_t CPowerUp::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 = 0;
	if (ulFileVersion < 20)
		{
		sResult	= CThing::Load(pFile, bEditMode, sFileCount, ulFileVersion);
		}
	else
		{
		// Note that we bypass CItem3d::Load() cuz I think that would be wierd.
		sResult	= CThing3d::Load(pFile, bEditMode, sFileCount, ulFileVersion);
		}

	if (sResult == 0)
		{
		CacheSample(g_smidPickedUpWeapon);
		switch (ulFileVersion)
			{
			default:
			case 31:
			case 30:
			case 29:
			case 28:
			case 27:
			case 26:
			case 25:
			case 24:
			case 23:
			case 22:
			case 21:
			case 20:
				// Base class gets it all.
				break;
			case 19:
			case 18:
			case 17:
				pFile->Read(&m_dX);
				pFile->Read(&m_dY);
				pFile->Read(&m_dZ);
				sResult	= m_stockpile.Load(pFile, ulFileVersion);
				break;
			case 16:
			case 15:
			case 14:
			case 13:
			case 12:
			case 11:
			case 10:
			case 9:
			case 8:
			case 7:
			case 6:
			case 5:
			case 4:
			case 3:
			case 2:
			case 1:
				{
				pFile->Read(&m_dX);
				pFile->Read(&m_dY);
				pFile->Read(&m_dZ);
				U8	u8Type	= (U8)CStockPile::Bullets; 
				pFile->Read(&u8Type);
				int32_t	lPowerVal;
				pFile->Read(&lPowerVal);
				switch (u8Type)
					{
					case 0://Bullets:
						m_stockpile.m_sNumBullets	= lPowerVal;
						break;
					case 1://Grenades:
						m_stockpile.m_sNumGrenades	= lPowerVal;
						break;
					case 2://Cocktails:
						m_stockpile.m_sNumFireBombs	= lPowerVal;
						break;
					case 3://Rockets:
						m_stockpile.m_sNumMissiles	= lPowerVal;
						break;
					case 4://Napalm:
						m_stockpile.m_sNumNapalms	= lPowerVal;
						break;
					case 5://Shells:
						m_stockpile.m_sNumShells	= lPowerVal;
						break;
					case 6://Fuel:
						m_stockpile.m_sNumFuel	= lPowerVal;
						break;
					case 7://ProximityMine:
					case 8://TimedMine:
					case 9://RemoteMine:
					case 10://BouncingBettyMine:
						m_stockpile.m_sNumMines	= lPowerVal;
						break;
					case 11://Health:
						m_stockpile.m_sHitPoints	= lPowerVal;
						break;
					case 12://Heatseekers:
						m_stockpile.m_sNumHeatseekers	= lPowerVal;
						break;
					}
				break;
				}
			}

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

	return sResult;
	}


////////////////////////////////////////////////////////////////////////////////
// Save object (should call base class version!)
////////////////////////////////////////////////////////////////////////////////
int16_t CPowerUp::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)
	{
	// Note that we bypass CItem3d::Save() cuz I think that would be wierd.
	int16_t	sResult	= CThing3d::Save(pFile, sFileCount);
	if (sResult == 0)
		{
		// Base class does it all.
		}

	return sResult;
	}


////////////////////////////////////////////////////////////////////////////////
// Update object
////////////////////////////////////////////////////////////////////////////////
void CPowerUp::Update(void)
	{
	if (!m_sSuspend)
		{
		// Get new time
		int32_t lThisTime = m_pRealm->m_time.GetGameTime();

		// Advance the animation timer.
		int32_t	lDifTime		= lThisTime - m_lAnimPrevUpdateTime;
		m_lAnimTime			+= lDifTime;

		// Update prev time.
		m_lAnimPrevUpdateTime	= lThisTime;

		// Service message queue.
		ProcessMessages();

		// Switch on state.
		switch (m_state)
			{
			case State_BlownUp:
				// Handle state.  If done . . .
				if (WhileBlownUp() == false)
					{
					m_state	= State_Idle;
					// Restore powerup cheese spin.
					m_dExtRotVelY	= Y_AXIS_ROTATION_RATE;
					m_dExtRotVelZ	= 0.0;
					m_dRotZ			= 0.0;
					}
				break;
			default:
				if (m_u16IdParent == CIdBank::IdNil)
					{
					// Get time from last call in seconds.
					double	dSeconds	= double(lThisTime - m_lPrevTime) / 1000.0;
			
					DeluxeUpdatePosVel(dSeconds);
					}

				break;
			}

		// 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	= m_sprite.m_sRadius;

		// Update the smash.
		m_pRealm->m_smashatorium.Update(&m_smash);

		// Save time for next time
		m_lPrevTime = lThisTime;
		}
	}

////////////////////////////////////////////////////////////////////////////////
// Render object
////////////////////////////////////////////////////////////////////////////////
void CPowerUp::Render(void)
	{
	CItem3d::Render();

	// Make larger.
	m_trans.Scale(SCALE, SCALE, SCALE);
	}

////////////////////////////////////////////////////////////////////////////////
// Setup object.
////////////////////////////////////////////////////////////////////////////////
int16_t CPowerUp::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 sResult = 0;
	
	// Use specified position
	m_dX = (double)sX;
	m_dY = (double)sY;
	m_dZ = (double)sZ;

	// Load resources and initialize.
	sResult = Init();

	if (sResult == 0)
		{
		sResult	= Startup();
		}

	return sResult;
	}


////////////////////////////////////////////////////////////////////////////////
// Called by editor to init new object at specified position
////////////////////////////////////////////////////////////////////////////////
int16_t CPowerUp::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;

	sResult	= EditModify();

	return sResult;
	}


////////////////////////////////////////////////////////////////////////////////
// Called by editor to modify object
////////////////////////////////////////////////////////////////////////////////
int16_t CPowerUp::EditModify(void)
	{
	int16_t	sResult	= m_stockpile.UserEdit();

	// If successful so far . . .
	if (sResult == 0)
		{
		// Load resources and initialize.
		sResult = Init();
		}

	return sResult;
	}


////////////////////////////////////////////////////////////////////////////////
// Initialize object.
////////////////////////////////////////////////////////////////////////////////
int16_t CPowerUp::Init(void)	// Returns 0 on success.
	{
	int16_t	sRes	= GetResources();

	// Prepare shadow (get resources and setup sprite).
	sRes	|= PrepareShadow();

	m_dExtRotVelY	= Y_AXIS_ROTATION_RATE;

	m_lAnimTime			= 0;

	// Set up collision object
	m_smash.m_bits		= CSmash::PowerUp | CSmash::Misc;

	return sRes;
	}

////////////////////////////////////////////////////////////////////////////////
// Get resource name for this item.
////////////////////////////////////////////////////////////////////////////////
void CPowerUp::GetResName(	// Returns nothing.
	char*	pszResName)			// Out: Resource base name.
	{
	int16_t	sTypeIndex;
	int16_t	sTypeInPowerUp	= CStockPile::NumStockPileItems;
	for (sTypeIndex = 0; sTypeIndex < CStockPile::NumStockPileItems; sTypeIndex++)
		{
		if (m_stockpile.GetItem(sTypeIndex) > 0)
			{
			// If no type yet . . .
			if (sTypeInPowerUp == CStockPile::NumStockPileItems)
				{
				sTypeInPowerUp	= sTypeIndex;
				}
			else
				{
				sTypeInPowerUp	= CStockPile::NumStockPileItems + 1;
				break;
				}
			}
		}

	strcpy(pszResName, ms_apszPowerUpResNames[sTypeInPowerUp]);
	}

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

	// Safe to call even if no resource.
	FreeResources();

	char	szResName[RSP_MAX_PATH];
	GetResName(szResName);

	sResult	= m_anim.Get(
		szResName, 
		NULL,
		NULL,
		NULL,
		RChannel_LoopAtStart | RChannel_LoopAtEnd);

	return sResult;
	}

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

	if (m_anim.m_psops != NULL)
		{
		m_anim.Release();
		}

	return sResult;
	}


////////////////////////////////////////////////////////////////////////////////
// Preload - Preload assets needed so they can be cached and don't have to load
//			    during the game
////////////////////////////////////////////////////////////////////////////////
int16_t CPowerUp::Preload(CRealm* /*prealm*/)
{
	int16_t sResult = 0;

	int16_t i;
	CAnim3D anim;

	for (i = 0; i < CStockPile::NumStockPileItems + 2; i++)
	{
		sResult |= anim.Get(ms_apszPowerUpResNames[i], NULL, NULL, NULL, 0);
		anim.Release();			
	}	

	return sResult;
}

////////////////////////////////////////////////////////////////////////////////
// Call to grab this item.
////////////////////////////////////////////////////////////////////////////////
int16_t CPowerUp::Grab(		// Returns 0 on success..
	CSprite* psprParent)		// In:  Parent's sprite.
	{
	int16_t	sRes	= 0;	// Assume success.

	// If we are not already grabbed . . .
	if (m_sprite.m_psprParent == NULL)
		{
		m_dX	= 0.0;
		m_dY	= 0.0;
		m_dZ	= 0.0;

		psprParent->AddChild(&m_sprite);

		// Don't use this for now b/c the next line that removes the
		// smash from the smashatorium will be ineffective b/c the
		// base class will put it right back.
		ASSERT(0);

		// Remove collision thinger.
		m_pRealm->m_smashatorium.Remove(&m_smash);
		}
	else
		{
		// Already have a parent.
		sRes	= -1;
		}

	return sRes;
	}

////////////////////////////////////////////////////////////////////////////////
// Call to release this item.
////////////////////////////////////////////////////////////////////////////////
void CPowerUp::Drop(			// Returns nothing.
	int16_t sX,					// In:  Position from which to release.
	int16_t sY,					// In:  Position from which to release.
	int16_t sZ)					// In:  Position from which to release.
	{
	m_dX	= sX;
	m_dY	= sY;
	m_dZ	= sZ;

	// If we have a parent . . .
	if (m_sprite.m_psprParent != NULL)
		{
		// Remove from parent.
		m_sprite.m_psprParent->RemoveChild(&m_sprite);
		}
	}

////////////////////////////////////////////////////////////////////////////////
// Plays a sample corresponding to the type of powerup indicating it
// was picked up.
////////////////////////////////////////////////////////////////////////////////
void CPowerUp::PickUpFeedback(void)	// Returns nothing.
	{
	PlaySample(g_smidPickedUpWeapon, SampleMaster::UserFeedBack);
	}

////////////////////////////////////////////////////////////////////////////////
// If this powerup has nothing left, destroys itself.  Otherwise, chooses
// a new resource to represent its current contents.
// NOTE:  This function can cause this item to be destroyed.  Don't use
// a ptr to this object after calling this function.
////////////////////////////////////////////////////////////////////////////////
void CPowerUp::RepaginateNow(void)
	{
	// If not empty . . .
	if (IsEmpty() == false)
		{
		// If this fails, we'd better go away . . .
		if (GetResources() != 0)
			{
			delete this;
			}
		}
	else
		{
		// Done then.
		delete this;
		}
	}

////////////////////////////////////////////////////////////////////////////////
// Message Handlers.
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Handles a Shot_Message.
// (virtual).
////////////////////////////////////////////////////////////////////////////////
void CPowerUp::OnShotMsg(		// Returns nothing.
	Shot_Message* pshotmsg)		// In:  Message to handle.
	{
	// Eventually, it'd be cool to have the ammos respond to bullets.

	// Invoke base class implementation.
	CItem3d::OnShotMsg(pshotmsg);
	}

////////////////////////////////////////////////////////////////////////////////
// Handles an Explosion_Message.
// (virtual).
////////////////////////////////////////////////////////////////////////////////
void CPowerUp::OnExplosionMsg(			// Returns nothing.
	Explosion_Message* pexplosionmsg)	// In:  Message to handle.
	{
	// Break into smaller pieces and pass the message on . . .
	// Inventory.
	int16_t	sTypeIndex;
	bool	bFirst	= true;
	int16_t	sNumGenerated	= 0;
	for (sTypeIndex = 0; sTypeIndex < CStockPile::NumStockPileItems; sTypeIndex++)
		{
		if (m_stockpile.GetItem(sTypeIndex) > 0)
			{
			if (bFirst == false)
				{
				CPowerUp*	ppowerup;
				if (ConstructWithID(CPowerUpID, m_pRealm, (CThing**)&ppowerup) == 0)
					{
					// Transfer item.
					ppowerup->m_stockpile.GetItem(sTypeIndex)	= m_stockpile.GetItem(sTypeIndex);
					// Clear local item.
					m_stockpile.GetItem(sTypeIndex)				= 0;
					// Prepare for the cold, cruel, fire-infested realm.
					ppowerup->Setup(m_dX, m_dY, m_dZ);
					// Slap it some (bypass this function, though..that'd be a waste to 
					// do again).
					ppowerup->CItem3d::OnExplosionMsg(pexplosionmsg);
					// Note that another was created.
					sNumGenerated++;
					// Choose random trajectory.
					ppowerup->m_dExtHorzRot	= GetRandom() % 360;
					}
				else
					{
					TRACE("OnExplosionMsg(): Failed to create powerup.\n");
					}
				}
			else
				{
				bFirst	= false;
				}
			}
		}

	// If we created any new powerups . . .
	if (sNumGenerated)
		{
		// This one was altered.
		if (GetResources() != 0)
			{
			// Doh!
			delete this;
			return;
			}
		}

	// Invoke base class implementation.
	CItem3d::OnExplosionMsg(pexplosionmsg);
	}

////////////////////////////////////////////////////////////////////////////////
// Handles a Burn_Message.
// (virtual).
////////////////////////////////////////////////////////////////////////////////
void CPowerUp::OnBurnMsg(		// Returns nothing.
	Burn_Message* pburnmsg)		// In:  Message to handle.
	{
	// Eventually, it'd be cool to have the ammos respond to fire.

	// Invoke base class implementation.
	CItem3d::OnBurnMsg(pburnmsg);
	}

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