////////////////////////////////////////////////////////////////////////////////
//
// 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
//
// Warp.cpp
// Project: Postal
// 
// History:
//		06/02/97 JMI	Started.
//
//		06/06/97	JMI	Priority was being set incorrectly (was adding the height
//							of this object to our Z, but since our hotspot is at our
//							bottom, our priority is simply our Z).
//
//		06/07/97	JMI	Added WarpIn() and WarpInAnywhere() function bodies.
//							Also, added CreateWarpFromDude().
//
//		06/15/97	JMI	Moved initialization of ms_stockpile into warp.cpp since
//							the stockpile is non aggregatable.
//
//		06/16/97	JMI	Moved initialization of dude's original hitpoints such
//							that both warp types (ones where he is preallocated and
//							ones where he is allocated by the WarpIn called) set it.
//
//		06/17/97	JMI	Converted all occurrences of rand() to GetRand() and
//							srand() to SeedRand().
//
//		06/29/97	JMI	Converted EditRect(), EditRender(), and/or Render() to
//							use Map3Dto2D().
//
//		06/30/97	JMI	Now maps the Z to 3D when loading fileversions previous to
//							24.
//
//		07/09/97	JMI	Now uses m_pRealm->Make2dResPath() to get the fullpath
//							for 2D image components.
//
//		07/19/97	JMI	Added m_sRotY, the dude's initial rotation around the Y
//							axis.  Also, now passes a GUI to CStockPile::UserEdit()
//							with some settings for us.
//
//		08/03/97 BRH	Increased the starting hit points for the Dude if the
//							game difficulty is set to 11.
//
//		08/05/97	JMI	Changed priority to use Z position rather than 2D 
//							projected Y position.
//
//		08/08/97	JMI	Upped GUI_ID_ROT_Y from 101 to 1001 so it wouldn't 
//							conflict with the stockpile dialog.
//
//		08/10/97	JMI	Now the warp places the dude via a call to 
//							CDude::SetPosition().
//
//		08/14/97	JMI	Switched references to g_GameSettings.m_sDifficulty to
//							m_pRealm->m_flags.sDifficulty.
//
//		09/30/97	JMI	Filled in empty fields in ms_stockpile.  Although it is
//							not necessary b/c of the implicit filescope zero init,
//							it just seems like a good idea.  But they are still zero
//							(just using enums instead of 0s) so there should be no 
//							synch problems with old versions.
//
//		12/01/97 BRH	For multiplayer deathmatch, the warp sets the hit
//							points to WARP_DEATHMATCH_DEFAULT_HP which will be
//							set to an amount we determine to be more fun.  Right
//							now, most levels are set to 500 hit points, but for
//							deathmatch, its too hard to kill each other.
//
//////////////////////////////////////////////////////////////////////////////
//
// This CThing-derived class will represent places and settings for dudes to
// 'warp' in at/with.
//
//////////////////////////////////////////////////////////////////////////////
#define WARP_CPP

#include "RSPiX.h"

#include "warp.h"
#include "game.h"
#include "reality.h"

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

// This is for edit mode only.
#define WARP_IMAGE_FILENAME		"warp.bmp"

#define GUI_FILENAME					"res/editor/warp.gui"

#define GUI_ID_ROT_Y					1001

#define WARP_DEATHMATCH_DEFAULT_HP 100

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

// The stockpile of ammo and health used by all CWarps.
CStockPile	CWarp::ms_stockpile	=
	{
	// Use CDude defaults for stockpile.
	CDude::DefHitPoints,

	CDude::DefNumGrenades,
	CDude::DefNumFireBombs,
	CDude::DefNumMissiles,
	CDude::DefNumNapalms,
	CDude::DefNumBullets,
	CDude::DefNumShells,
	CDude::DefNumFuel,
	CDude::DefNumMines,
	CDude::DefNumHeatseekers,

	CDude::DefHasMachineGun,
	CDude::DefHasLauncher,
	CDude::DefHasShotgun,
	CDude::DefHasSprayCannon,
	CDude::DefHasFlamer,
	CDude::DefHasNapalmLauncher,	
	CDude::DefHasDeathWadLauncher,	
	CDude::DefHasDoubleBarrel,		

	CDude::DefKevlarLayers,
			                   
	CDude::DefHasBackpack,
	};

// Tracks file counter so we know when to load/save "common" data 
int16_t	CWarp::ms_sFileCount	= 0;

////////////////////////////////////////////////////////////////////////////////
// Load object (should call base class version!)
////////////////////////////////////////////////////////////////////////////////
int16_t CWarp::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)
		{
		// If statics have not yet been loaded . . .
		if (ms_sFileCount != sFileCount)
			{
			ms_sFileCount = sFileCount;

			ms_stockpile.Load(pFile, ulFileVersion);
			}

		switch (ulFileVersion)
			{
			default:
			case 32:
				pFile->Read(&m_sRotY);
			case 31:
			case 30:
			case 29:
			case 28:
			case 27:
			case 26:
			case 25:
			case 24:
			case 23:
			case 22:
			case 21:
			case 20:
			case 19:
			case 18:
			case 17:
			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);
				break;
			}

		// If the file version is earlier than the change to real 3D coords . . .
		if (ulFileVersion < 24)
			{
			// Convert to 3D.
			m_pRealm->MapY2DtoZ3D(
				m_dZ,
				&m_dZ);
			}

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

	return sResult;
	}


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

			ms_stockpile.Save(pFile);
			}

		pFile->Write(&m_sRotY);
		pFile->Write(&m_dX);
		pFile->Write(&m_dY);
		pFile->Write(&m_dZ);

		// Make sure there were no file errors
		sResult	= pFile->Error();
		}

	return sResult;
	}


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


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


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


////////////////////////////////////////////////////////////////////////////////
// Resume object
////////////////////////////////////////////////////////////////////////////////
void CWarp::Resume(void)
	{
	m_sSuspend--;
	}

////////////////////////////////////////////////////////////////////////////////
// Update object
////////////////////////////////////////////////////////////////////////////////
void CWarp::Update(void)
	{
	// Do schtuff.
	}

////////////////////////////////////////////////////////////////////////////////
// Render object
////////////////////////////////////////////////////////////////////////////////
void CWarp::Render(void)
	{
	// Doesn't normally draw anything (see EditRender() for render during
	// edit mode).
	}


////////////////////////////////////////////////////////////////////////////////
// Called by editor to init new object at specified position
////////////////////////////////////////////////////////////////////////////////
int16_t CWarp::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	= Init();

	return sResult;
	}


////////////////////////////////////////////////////////////////////////////////
// Called by editor to modify object
////////////////////////////////////////////////////////////////////////////////
int16_t CWarp::EditModify(void)
	{
	int16_t	sResult	= 0;
	// Load our GUI.
	RGuiItem* pgui = RGuiItem::LoadInstantiate(FullPathVD(GUI_FILENAME));
	if (pgui)
		{
		// Set current Y axis rotation.
		RGuiItem*	pguiRotY	= pgui->GetItemFromId(GUI_ID_ROT_Y);
		if (pguiRotY)
			{
			pguiRotY->SetText("%d", m_sRotY);
			pguiRotY->Compose();
			}

		// Display stockpile's GUI with ours at the bottom.
		sResult	= ms_stockpile.UserEdit(pgui);

		// If not cancelled . . .
		if (sResult == 0)
			{
			if (pguiRotY)
				{
				m_sRotY	= pguiRotY->GetVal();
				}
			}

		// Destroy our GUI.
		delete pgui;
		}

	return sResult;
	}


////////////////////////////////////////////////////////////////////////////////
// Called by editor to move object to specified position
////////////////////////////////////////////////////////////////////////////////
int16_t CWarp::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 get the clickable pos/area of an object in 2D.
// (virtual	(Overridden here)).
////////////////////////////////////////////////////////////////////////////////
void CWarp::EditRect(	// Returns nothiing.
	RRect*	prc)				// Out: Clickable pos/area of object.
	{
	Map3Dto2D(
		m_dX,
		m_dY,
		m_dZ,
		&(prc->sX),
		&(prc->sY) );

	prc->sW	= 10;	// Safety.
	prc->sH	= 10;	// Safety.

	if (m_sprite.m_pImage != NULL)
		{
		prc->sW	= m_sprite.m_pImage->m_sWidth;
		prc->sH	= m_sprite.m_pImage->m_sHeight;
		}

	prc->sX	-= prc->sW / 2;
	prc->sY	-= prc->sH;
	}

////////////////////////////////////////////////////////////////////////////////
// Called by editor to get the hotspot of an object in 2D.
// (virtual	(Overridden here)).
////////////////////////////////////////////////////////////////////////////////
void CWarp::EditHotSpot(	// Returns nothiing.
	int16_t*	psX,					// Out: X coord of 2D hotspot relative to
										// EditRect() pos.
	int16_t*	psY)					// Out: Y coord of 2D hotspot relative to
										// EditRect() pos.
	{
	*psX	= 0;	// Safety.
	*psY	= 0;	// Safety.

	if (m_sprite.m_pImage != NULL)
		{
		*psX	= m_sprite.m_pImage->m_sWidth / 2;
		*psY	= m_sprite.m_pImage->m_sHeight;
		}
	}

////////////////////////////////////////////////////////////////////////////////
// Called by editor to update object
////////////////////////////////////////////////////////////////////////////////
void CWarp::EditUpdate(void)
	{
	// The editor schtuff.
	}

////////////////////////////////////////////////////////////////////////////////
// Called by editor to render object
////////////////////////////////////////////////////////////////////////////////
void CWarp::EditRender(void)
	{
	// Map from 3d to 2d coords
	Map3Dto2D(
		(int16_t) m_dX, 
		(int16_t) m_dY, 
		(int16_t) m_dZ, 
		&m_sprite.m_sX2, 
		&m_sprite.m_sY2);

	// Priority is based on bottom edge of sprite on viewing plane.
	m_sprite.m_sPriority = m_dZ;
		
	// Center on image.
	m_sprite.m_sX2	-= m_sprite.m_pImage->m_sWidth / 2;
	m_sprite.m_sY2	-= m_sprite.m_pImage->m_sHeight;

	// 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));

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


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

	return sRes;
	}

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

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

	sResult	= rspGetResource(
		&g_resmgrGame, 
		m_pRealm->Make2dResPath(WARP_IMAGE_FILENAME),
		&m_sprite.m_pImage);

	return sResult;
	}


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

	if (m_sprite.m_pImage != NULL)
		{
		rspReleaseResource(&g_resmgrGame, &m_sprite.m_pImage);
		}

	return sResult;
	}


////////////////////////////////////////////////////////////////////////////////
// Stocks, rejuvenates, and places a CDude.  The dude can be passed to this
// function or allocated by this function.
////////////////////////////////////////////////////////////////////////////////
int16_t CWarp::WarpIn(	// Returns 0 on success.
	CDude**	ppdude,	// In:  CDude to 'warp in', *ppdude = NULL to create one.
							// Out: Newly created CDude, if no CDude passed in.
	int16_t	sOptions)	// In:  Options for 'warp in'.
	{
	int16_t	sRes	= 0;	// Assume success.

	// If we are on difficulty 11, multiply the dude's hit points by 10 so that
	// the player can have some chance of playing.
	if (m_pRealm->m_flags.sDifficulty == 11)
	{
		ms_stockpile.m_sHitPoints *= 10;
		if (ms_stockpile.m_sHitPoints < 0)
			ms_stockpile.m_sHitPoints = 30000;
	}

	// If we are playing multiplayer deathmatch, then set the hitpoints to the
	// default deathmatch amount so that it is easier to kill each other 
	// and thus more fun.
	if (m_pRealm->m_flags.bMultiplayer == true && m_pRealm->m_flags.bCoopMode == false)
	{
		ms_stockpile.m_sHitPoints = WARP_DEATHMATCH_DEFAULT_HP;
	}

	// If no dude passed . . .
	if (*ppdude == NULL)
		{
		sRes	= ConstructWithID(CDudeID, m_pRealm, (CThing**)ppdude);
		if (sRes == 0)
			{
			// Copy stockpile to new CDude.
			(*ppdude)->m_stockpile.Copy(&ms_stockpile);
			// Initialize.
			sRes	= (*ppdude)->Init();
			if (sRes == 0)
				{
				// Start up.
				sRes	= (*ppdude)->Startup();
				if (sRes == 0)
					{
					// Successfully created and setup CDude.
					}
				else
					{
					TRACE("WarpIn(): (*ppdude)->Startup() failed.\n");
					}
				}
			else
				{
				TRACE("WarpIn(): (*ppdude)->Init() failed.\n");
				}
			}
		else
			{
			TRACE("WarpIn(): Construct() failed for CDude.\n");
			}
		}
	else
		{
		switch (sOptions & StockPileMask)
			{
			case CopyStockPile:
				// Copy stockpile to CDude.
				(*ppdude)->m_stockpile.Copy(&ms_stockpile);
				break;
			case UnionStockPile:
				// Union stockpile with CDude.
				(*ppdude)->m_stockpile.Union(&ms_stockpile);
				break;
			case AddStockPile:
				// Add stockpile to CDude.
				(*ppdude)->m_stockpile.Add(&ms_stockpile);
				break;
			}
		}

	// If successful so far . . .
	if (sRes == 0)
		{
		// Truncate to amount he can carry.
		(*ppdude)->m_stockpile.Truncate();

		if ((*ppdude)->m_stockpile.m_sHitPoints > 0)
			{
			// Base original hitpoints upon new settings.
			(*ppdude)->m_sOrigHitPoints	= (*ppdude)->m_stockpile.m_sHitPoints;
			}

		// Place.
		(*ppdude)->SetPosition(m_dX, m_dY, m_dZ);
		// Orient.
		(*ppdude)->m_dRot	= m_sRotY;
		}

	return sRes;
	}

////////////////////////////////////////////////////////////////////////////////
// Stocks, rejuvenates, and places a CDude at a random warp.  The dude can 
// be passed to this function or allocated by this function.
// (static)
////////////////////////////////////////////////////////////////////////////////
int16_t CWarp::WarpInAnywhere(	// Returns 0 on success.
	CRealm*	prealm,				// In:  Realm in which to choose CWarp.
	CDude**	ppdude,				// In:  CDude to 'warp in', *ppdude = NULL to create one.
										// Out: Newly created CDude, if no CDude passed in.
	int16_t	sOptions)				// In:  Options for 'warp in'.
	{
	int16_t	sRes	= 0;	// Assume success.

	// Find a warp:
	
	int16_t	sNumWarps	= prealm->m_asClassNumThings[CWarpID];
	if (sNumWarps > 0)
		{
		// Pick a random warp number.
		int16_t	sWarpNum	= GetRand() % sNumWarps;
		// Find that warp.
		int16_t	i;
		CListNode<CThing>*	pln		= prealm->m_aclassHeads[CWarpID].m_pnNext;
		CListNode<CThing>*	plnTail	= &(prealm->m_aclassTails[CWarpID]);
		for (i = 0; i < sWarpNum && pln != plnTail; i++, pln = pln->m_pnNext)
			;
	
		// If we found one . . .
		if (pln != plnTail)
			{
			ASSERT(pln->m_powner != NULL);

			// Do it.
			sRes	= ((CWarp*)(pln->m_powner) )->WarpIn(ppdude, sOptions);
			}
		else
			{
			TRACE("WarpInAnywhere(): Failed to find chosen CWarp.\n");
			sRes	= -2;
			}
		}
	else
		{
		TRACE("WarpInAnywhere(): Specified realm contains no CWarps.\n");
		sRes	= -1;
		}

	return sRes;
	}

////////////////////////////////////////////////////////////////////////////////
// Creates a warp based on a dude's settings.
// (static)
////////////////////////////////////////////////////////////////////////////////
int16_t CWarp::CreateWarpFromDude(	// Returns 0 on success.
	CRealm*	prealm,					// In:  Realm in which to choose CWarp.
	CDude*	pdude,					// In:  Dude to create warp from.
	CWarp**	ppwarp,					// Out: New warp on success.
	bool		bCopyStockPile)		// In:  true to copy stockpile, false otherwise.
	{
	int16_t	sRes	= 0;	// Assume success.

	// Create warp . . .
	if (ConstructWithID(CWarpID, prealm, (CThing**)ppwarp) == 0)
		{
		// Copy dude's position and orientation.
		(*ppwarp)->m_dX		= pdude->m_dX;
		(*ppwarp)->m_dY		= pdude->m_dY;
		(*ppwarp)->m_dZ		= pdude->m_dZ;
		(*ppwarp)->m_sRotY	= pdude->m_dRot;

		if (bCopyStockPile == true)
			{
			// Copy dude's stockpile.
			// Note that this is static member of warp so this only needs to be for
			// one, but the question is which one.
			(*ppwarp)->ms_stockpile.Copy( &(pdude->m_stockpile) );
			}
		}
	else
		{
		TRACE("CreateWarpFromDude(): ConstructWithID() failed.\n");
		sRes	= -1;
		}

	return sRes;
	}

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