////////////////////////////////////////////////////////////////////////////////
//
// 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
//
// cutscene.cpp
// Project: Postal
//
// This module handles all the cut-scenes.
//
// History:
//		04/18/97 MJR	Started.
//
//		04/20/07 BRH	Problem compiling this file due to RPrint print being
//							defined more than once.  It may have just been cut and
//							pasted code.
//
//		04/21/97 MJR	Created real version of CutScene().
//
//		04/22/97 MJR	Testing and tuning.
//
//		04/26/97 JRD	Added a swirling blur effect to the cutscenes
//
//		04/27/97 JRD	Merged CSwirl::Configure with CutScene to allow greater flxibility
//
//		06/08/97 MJR	Changed interface in order to properly clean everything
//							up without memory leaks.
//
//		06/11/97	JMI	Changed so CutSceneStart() does not require a prefs file
//							and section name but instead opens and creates them using
//							a single realm number that is now passed in.
//
//		06/12/97 MJR	Renamed m_pszRealm to m_pszRealmPrefsFile.
//
//		06/14/97 MJR	Fixed problem regarding default multialpha name and what
//							happens if none gets loaded (i.e. - it crashed).
//
//					MJR	Added support for sepearate network sections in realms
//							prefs file.
//
//		06/17/97	JMI	Added NULL in call to PlaySample() corresponding to new
//							param.
//
//		07/05/97 MJR	Changed to RSP_BLACK_INDEX instead of 0.
//
//		07/14/97 BRH	Added challenge mode parameter to CutSceneStart so that
//							the proper text can be displayed for the Gauntlet.
//
//		07/17/97	JMI	Changed RSnd*'s to SampleMaster::SoundInstances.
//							Now uses new SampleMaster interface for volume and play
//							instance reference.
//
//		07/18/97 MJR	Got progress bars working in a ROUGH sort of way.  They
//							still need some tuning to look better and we need to save
//							and load the "total bytes" (aka cumm units) for each
//							realm to the prefs file.
//
//		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/20/97	JMI	Added NUM_ELEMENTS() macro as alternative to 
//							(sizeof(a)/sizeof(a[0]) ).
//							Changed asWavyY to be of type short (was no specific type
//							and, therefore, int before).
//							Fix parenthesis bug in CutScene_RFileCallback() with index
//							that caused it to hit -1.
//
//		08/18/97 BRH	Changed default cutscene from cut00 to abstract.
//
//		08/18/97	JMI	Now works with the four different combinations of missing 
//							assets I could conceive.
//
//		08/20/97	JMI	Now uses source clipping when copying the progress bar
//							area out of m_pimBGLayer for safety (normally they should
//							be the correct size).
//
//		08/21/97	JMI	Changed call to Update() to UpdateSystem() and occurrences
//							of rspUpdateDisplay() to UpdateDisplay().
//
//		08/22/97 BRH	Added ability to load a different sound file for each
//							cutscene, specified in the realms.ini file.
//
//		08/22/97	JMI	Changed calls to UpdateDisplay() back to rspUpdateDisplay()
//							since we no longer need UpdateDisplay() now that we are
//							using rspLock/Unlock* functions properly.
//							Also, now set the PlaySample() call to purge the sample
//							when done.
//
//		08/27/97	JRD	Adding a compact stand along Martini effect for us with the 
//							end of the game called MartiniDo
//
//		08/27/97	JRD	Made MartiniDo WAY to easy for Bill to use, by putting up 
//							with him justpassing the whole screen buffer, me doing a 
//							general lock on it, creating a temporary bmp, and copying
//							it in.  Happy Bill?
//
//		08/28/97 JRD	Refined martini effect and added fade out option.
//
//		08/30/97 BRH	Fixed path for loading the realms.ini file.  It now loads
//							from the CD path rather thant the HD path.
//
//		09/08/97 BRH	I just learned about rspGetQuitStatus() and found it to
//							be so much fun, that I just started adding it to every
//							function I could find, including MartiniDo()
//
//		09/11/97	JMI	Added some protection for when the cutscene image is not
//							available.  Only tested for missing cutscenes with CompUSA
//							version though which doesn't let you get to the martini
//							effect so that may still not work without a BMP (although,
//							I did put in the appropriate checking, it may still have
//							side effects of using different numbers than the real 
//							cutscene would (like say a width of 0) ).
//
//		09/24/97	JMI	Now initializes bFemalePain member of m_musicID.  This 
//							member indicates whether the sample is of a female in pain 
//							which some countries (so far just UK) don't want in the 
//							game.
//
//		10/07/97	JMI	Changed bFemalePain to usDescFlags.
//
//		06/03/98 BRH	Had to change the path for the realms.ini file back to
//							loading from the HD for the add-on pack since the
//							new cutscenes are now only stored on the HD while the
//							original PostalCD is in their CD drive (it only has
//							the old cutscene images).
//
//		02/04/00 MJR	Changed so that it now checks the HD first and the VD
//							second when trying to load bg's and multialphas.  This
//							was done to fix problems with the Japan Add On.
//
////////////////////////////////////////////////////////////////////////////////

#include "RSPiX.h"
#include "cutscene.h"
#include "game.h"
#include "update.h"
#include "SampleMaster.h"
#include "AlphaAnimType.h"


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

#define FONT_FILE					"res/fonts/smash.fnt"

#define DEFAULT_TITLE			"Loading..."
#define DEFAULT_BG				"res/cutscene/abstract.bmp"
#define DEFAULT_TEXT				""
#define DEFAULT_MUSIC			"psychedelic1.wav"

#define BG_X						(ms_pCut->m_pimDst->m_sWidth / 2  - ms_pCut->m_pimBGLayer->m_sWidth / 2)
#define BG_Y						(ms_pCut->m_pimDst->m_sHeight / 2 - ms_pCut->m_pimBGLayer->m_sHeight / 2)

#define NAME_Y						85
#define TEXT_Y						(280 - 48) 

#define MARGIN						24

#define BLOOD_FORE_R				128 //200
#define BLOOD_FORE_G				0 //10
#define BLOOD_FORE_B				0 //10

#define FONT_FORE_R				255 //180
#define FONT_FORE_G				0 //10
#define FONT_FORE_B				0 //10

#define FONT_SHAD_R				0
#define FONT_SHAD_G				0
#define FONT_SHAD_B				0

// Starting position for progress bar
#define PROGRESS_BAR_START_X		165
#define PROGRESS_BAR_START_Y		460

// Width of progress bar
#define PROGRESS_BAR_WIDTH			310

// Random values for creating spread of pixels (it does +/- these values!!!!)
#define PROGRESS_BAR_RANDOM_X		7
#define PROGRESS_BAR_RANDOM_Y		2

// Number of pixels to plot each time
#define PROGRESS_BAR_DENSITY		10

// How often to update the progress bar (in milliseconds)
#define PROGRESS_BAR_UPDATE_TIME	250

// Area of the screen that needs to be updated (keep 16-byte aligned for speed!!!)
#define PROGRESS_BOX_X			96
#define PROGRESS_BOX_Y			440
#define PROGRESS_BOX_WIDTH		448
#define PROGRESS_BOX_HEIGHT	40

// Get a random number plus/minus the specified value
#define RandomPlusMinus(x)		((MyRandom() % (((x) + 1) * 2)) - (x))

// Determines the number of elements in the passed array at compile time.
#define NUM_ELEMENTS(a)		(sizeof(a) / sizeof(a[0]) )


////////////////////////////////////////////////////////////////////////////////
//
// Local random stuff
//
////////////////////////////////////////////////////////////////////////////////
static int ms_lRandom;

inline void MySeedRandom(int32_t seed)
	{
	ms_lRandom = seed;
	}

inline int32_t MyRandom(void)
	{
	return (((ms_lRandom = ms_lRandom * 214013L + 2531011L) >> 16) & 0x7fff);
	}


////////////////////////////////////////////////////////////////////////////////
//
//
////////////////////////////////////////////////////////////////////////////////
class	CCutSceneInfo
	{
	public:
		//-----------------------------------------
		RFont* m_pFont;
		char	m_szTitle[256];
		char	m_szText[4096];
		char	m_szMusic[RSP_MAX_PATH];
		uint8_t	m_ucForeText;
		uint8_t	m_ucShadowText;
		RImage*	m_pimBGLayer;
		RImage*	m_pimTextLayer;
		RImage*	m_pimDst;
		RMultiAlpha*	m_pmaAlpha;
		bool m_bDeleteFont;
		int16_t m_sDelW;
		int16_t m_sDelH;
		int32_t m_lTotalBytes;
		int32_t m_lTimeToUpdate;
		int32_t m_lBytesSoFar;
		U8 m_u8BloodColor;
		int16_t m_sLastDistance;
		SampleMasterID m_musicID;
		//-----------------------------------------

		void	Clear()
			{
			m_pFont = NULL;
			m_szTitle[0] = 0;
			m_szText[0] = 0;
			m_szMusic[0] = 0;
			m_ucForeText = 0;
			m_ucShadowText = 0;
			m_pimBGLayer = NULL;
			m_pimTextLayer = NULL;
			m_pimDst = NULL;
			m_pmaAlpha = NULL;
			m_bDeleteFont = true;
			m_sDelW = 0;
			m_sDelH = 0;
			m_lTotalBytes = 0;
			m_lTimeToUpdate = 0;
			m_lBytesSoFar = 0;
			m_u8BloodColor = 0;
			m_sLastDistance = 0;
			m_musicID = g_smidNil;
			}

		CCutSceneInfo()
			{
			Clear();
			}

		~CCutSceneInfo()
			{
			if (m_bDeleteFont && m_pFont) delete m_pFont;
			if (m_pimBGLayer) delete m_pimBGLayer;
			if (m_pimTextLayer) delete m_pimTextLayer;
			if (m_pmaAlpha) delete m_pmaAlpha;

			Clear();
			}
	};

////////////////////////////////////////////////////////////////////////////////
//
//		MartiniDo - this is a greatly simplified version of the cutscene Martini
//
//		It allows the user a few parameters and then does the effect for a set
//		amount of time, regardless of user input.  There are no text overlays.
//		The program will blacken the screen when done.  (g_pimScreenBuf)
//
////////////////////////////////////////////////////////////////////////////////
int16_t	MartiniDo(	RImage*	pimBackground,	// actually, this is the ONLY graphic
						int16_t	sStartX,				// logical start position of image
						int16_t	sStartY,				// NOTE: it will be clipped so won't actually hit this point!
						RMultiAlpha*	pAlpha,	// only need 50% - see cut scenes
						int32_t	lMilliLen,			// how long to do the effect
						int16_t	sRadius,				// Your tuning pleasure
						int32_t	lSpinTime,			// in milliseconds
						int32_t	lSwayTime,			// in milliseconds
						RRect*  prCenter,			// if not NULL, use this portion of the image only!
						int32_t	lFadeTime,			// fade to black, in milliseconds.
						SampleMaster::SoundInstance siFade	// to make sound fade out
					)
	{
	// In casew Bill just haphazardly passed me the screen buffer, make a dubber copy:
	RImage imSwirl;

	rspGeneralLock(pimBackground);
	if (prCenter)
		{
		
		imSwirl.CreateImage(prCenter->sW,prCenter->sH,RImage::BMP8);

		rspBlit(pimBackground,&imSwirl,
		prCenter->sX,prCenter->sY,0,0,
		prCenter->sW,prCenter->sH);

		sStartX += prCenter->sX;
		sStartY += prCenter->sY;

		}
		else
		{
		imSwirl.CreateImage(pimBackground->m_sWidth,pimBackground->m_sHeight,RImage::BMP8);


		rspBlit(pimBackground,&imSwirl,0,0,0,0,pimBackground->m_sWidth,pimBackground->m_sHeight);	

		}

	rspGeneralUnlock(pimBackground);

	rspLockBuffer();
	rspRect(RSP_BLACK_INDEX,g_pimScreenBuf,0,0,g_pimScreenBuf->m_sWidth,g_pimScreenBuf->m_sHeight);
	rspUnlockBuffer();	// Dom't show th black!

	//==============================================================================
	// Set up clipping parameters based on radius:
	RRect	rClip(sStartX,sStartY,imSwirl.m_sWidth,imSwirl.m_sHeight);

	// shrink by radius (should work! - this should hide all redrawing!)

	rClip.sX += sRadius;
	rClip.sY += sRadius;
	rClip.sW -= sRadius<<1;
	rClip.sH -= sRadius<<1;
	//==============================================================================
	// Copy the palette:
	uint8_t PaletteCopy[256 * 3] = {0,};
	rspGetPaletteEntries(10,236,PaletteCopy+30,PaletteCopy + 31,PaletteCopy + 32,3);

	int32_t lStartTime = rspGetMilliseconds();
	int32_t	lCurrentTime = -1;

	int32_t	lStartFadeTime = lMilliLen - lFadeTime;  // lCurrentTime is relative to lStartTime

	// Attempt to do a fade out while swirling....
	while (((lCurrentTime = (rspGetMilliseconds() - lStartTime)) < lMilliLen) && !(rspGetQuitStatus()))
		{
		int32_t	lCurrentFadeTime = lCurrentTime - lStartFadeTime;
		//==============================================================================
		// Figure out stage of motion:
		//==============================================================================
		int16_t sDeg = int16_t((360 * lCurrentTime)/lSpinTime);
		sDeg = rspMod360(sDeg);
		int16_t sSwayDeg = int16_t((360 * lCurrentTime)/lSwayTime);
		sSwayDeg = rspMod360(sSwayDeg);
		
		int16_t sR = int16_t(sRadius * rspSin(sSwayDeg));
		int16_t sOffX = int16_t(rspCos(sDeg) * sR);
		int16_t sOffY = int16_t(rspSin(sDeg) * sR);
		//==============================================================================
		// Draw the frame!
		rspLockBuffer();

		// Opaque Blit!
		rspBlit(&imSwirl,g_pimScreenBuf,0,0,sStartX + sOffX,sStartY + sOffY,
						imSwirl.m_sWidth,imSwirl.m_sHeight,&rClip);
		// Alpha Blit!
		rspAlphaBlitT(128,pAlpha,&imSwirl,g_pimScreenBuf,
						sStartX - sOffX,sStartY - sOffY,&rClip);

		rspUnlockBuffer();
		rspUpdateDisplay();
		//---------------------------------------
		// Do a palette fade out:
		if (lCurrentFadeTime > 0)
			{
			// Update the Palette:
			int16_t i=0;

			int16_t sCurPal = 10 * 3;
			int16_t sByteLevel = int16_t((255.0 * lCurrentFadeTime) / lFadeTime);

			for (i=10; i < 246; i++, sCurPal += 3)
				{
				rspSetPaletteEntry( i,
						(PaletteCopy[sCurPal + 0] * (256 - sByteLevel)) / 256,
						(PaletteCopy[sCurPal + 1] * (256 - sByteLevel)) / 256,
						(PaletteCopy[sCurPal + 2] * (256 - sByteLevel)) / 256
						);
				}

			rspUpdatePalette();

			if (siFade)	// adjust sound volume:
				{
				SetInstanceVolume(siFade,255 - sByteLevel);
				}
			}

		//---------------------------------------
		UpdateSystem();
		}
	//==============================================================================
	// Now do a fade out
	//==============================================================================
	rspLockBuffer();
	rspRect(RSP_BLACK_INDEX,g_pimScreenBuf,0,0,g_pimScreenBuf->m_sWidth,g_pimScreenBuf->m_sHeight);
	rspUpdateDisplay();
	rspUnlockBuffer();

	return SUCCESS;
	}


////////////////////////////////////////////////////////////////////////////////
//
//
////////////////////////////////////////////////////////////////////////////////
class	CSwirlMe
	{
	public:
		//------------------------------------------------------------------
		int16_t m_sCenX;		// radius in pixels
		int16_t m_sRadX;
		int16_t m_sCenY;		// radius in pixels
		int16_t m_sRadY;
		double m_dCenA;	// Alpha level (0.0 to 1.0)
		double m_dRadA;
		int32_t	m_lCycleTimeX;	// in milliseconds (Min, Max, Min)
		int32_t	m_lCycleTimeY;	// in milliseconds (Min, Max, Min)
		int32_t m_lCycleTimeA;	//
		int32_t m_lTimeSpin;
		int32_t	m_lBaseTime;
		RRect	m_rClip;			// To offset and limit the effect to a rect:
		SampleMaster::SoundInstance m_siSound;
		CCutSceneInfo* m_pCut;
		//------------------------------------------------------------------

		////////////////////////////////////////////////////////////////////////////
		//		Make next frame of effect
		////////////////////////////////////////////////////////////////////////////
		int16_t	MakeFrame(SampleMasterID* psmid)
			{
			if (!IsSamplePlaying(*psmid))
				{
				PlaySample(										// Returns nothing.
																	// Does not fail.
					*psmid,										// In:  Identifier of sample you want played.
					SampleMaster::Unspecified,				// In:  Sound Volume Category for user adjustment
					255,											// In:  Initial Sound Volume (0 - 255)
					&m_siSound,									// Out: Handle for adjusting sound volume
					NULL,											// Out: Sample duration in ms, if not NULL.
					0,												// In:  Where to loop back to in milliseconds.
																	//	-1 indicates no looping (unless m_sLoop is
																	// explicitly set).
					0,												// In:  Where to loop back from in milliseconds.
																	// In:  If less than 1, the end + lLoopEndTime is used.
					true);										// In:  Call ReleaseAndPurge rather than Release after playing
				}

			if (!m_lBaseTime) m_lBaseTime = rspGetMilliseconds();

			int32_t lDeltaTime = rspGetMilliseconds() - m_lBaseTime;

			int16_t sDegX = (360 * lDeltaTime) / m_lCycleTimeX;
			int16_t sDegY = (360 * lDeltaTime) / m_lCycleTimeY;
			int16_t sDegAlpha = (360 * lDeltaTime) / m_lCycleTimeA;
			int16_t sDegSpin = (360 * lDeltaTime) / m_lTimeSpin;

			sDegX = rspMod360(sDegX);
			sDegY = rspMod360(sDegY);
			sDegAlpha = rspMod360(sDegAlpha);
			sDegSpin = rspMod360(sDegSpin);

			int16_t	sRx = m_sCenX + int16_t(rspSin(sDegX) * m_sRadX);
			int16_t	sRy = m_sCenY + int16_t(rspSin(sDegY) * m_sRadY);

			if (sRx < 0) sRx = 0;
			if (sRy < 0) sRy = 0;

			int16_t sOffX = int16_t(rspCos(sDegSpin) * sRx);
			int16_t sOffY = -int16_t(rspSin(sDegSpin) * sRy);
			sOffX >>= 1;
			sOffY >>= 1;

			double dAlpha = m_dCenA + rspSin(sDegAlpha) * m_dRadA;

			//----------------------- Use global alpha --------------------
			RRect rSafeClip = m_rClip;

			rSafeClip.sX += m_sRadX;
			rSafeClip.sY += m_sRadY;

			rSafeClip.sW -= (m_sRadX<<1);
			rSafeClip.sH -= (m_sRadY<<1);

//******************************************************************************
//  THIS IS A COMPLETE HACK - IT IS A LAST MINUTE ATTEMPT TO ADJUST FOR THE
//  CHANGE TO THE CUT SCENE ASSEST WHICH ADDED BLACK STRIPS TO THE TOP AND
//  BOTTOM OF THE SCREEN....
//******************************************************************************
			rSafeClip.sY += 40; // HARD CODED FOR POSTAL!
			rSafeClip.sH -= 80; // HARD CODED FOR POSTAL!
//******************************************************************************

			// 1) Put original image down upon target:

			if (m_pCut->m_pimBGLayer)
				{
				rspLockBuffer();

				rspBlit(m_pCut->m_pimBGLayer,m_pCut->m_pimDst,0,0,m_rClip.sX - sOffX,m_rClip.sY - sOffY,
						m_rClip.sW,m_rClip.sH,&rSafeClip);

				// 2) Alpha Blit Upon it:
				if (m_pCut->m_pmaAlpha != NULL)
					{
					rspAlphaBlitT(int16_t(255.9*dAlpha),m_pCut->m_pmaAlpha,m_pCut->m_pimBGLayer,m_pCut->m_pimDst,
						m_rClip.sX + sOffX,m_rClip.sY + sOffY,&rSafeClip);
					}

				// 3) GENLOCK the text:
				rspBlit(m_pCut->m_pimTextLayer,m_pCut->m_pimDst,0,0);


				// 4) You're done - let the app update the screen -> he can use
				// the given function or do it himself!

				rspUnlockBuffer();
				}


			return SUCCESS;
			}

		////////////////////////////////////////////////////////////////////////////
		//		Configure effect
		////////////////////////////////////////////////////////////////////////////
		int16_t Configure(//RImage* pimDst,RImage* pimSrc,RMultiAlpha* pX,
					int32_t lTimeSpin,
					int16_t sMinX,int16_t sMaxX,int32_t lTimeX,
					int16_t sMinY,int16_t sMaxY,int32_t lTimeY,
					double dMinA,double dMaxA,int32_t lTimeA,
					int16_t sX,int16_t sY,int16_t sW,int16_t sH)
			{
			ASSERT(m_pCut);
			ASSERT(lTimeX > 0);
			ASSERT(lTimeY > 0);
			ASSERT(lTimeA > 0);
			ASSERT(lTimeSpin > 0);
			ASSERT((dMinA >= 0.0) && (dMinA <= 1.0));
			ASSERT((dMaxA >= 0.0) && (dMaxA <= 1.0));
			ASSERT(sW > 0);
			ASSERT(sH> 0);
			//----------------------------------------------
			m_sRadX = (sMaxX - sMinX)>>1;
			m_sCenX = sMinX + m_sRadX;
			m_sRadY = (sMaxY - sMinY)>>1;
			m_sCenY = sMinY + m_sRadY;
			m_dRadA = (dMaxA - dMinA)/2.0;
			m_dCenA = dMinA + m_dRadA;
			m_lCycleTimeX = lTimeX;
			m_lCycleTimeY = lTimeY;
			m_lCycleTimeA = lTimeA;
			m_lTimeSpin = lTimeSpin;

			// Center the effect!
			if (m_pCut->m_pimBGLayer)
				{
				m_pCut->m_sDelW = m_pCut->m_pimDst->m_sWidth - m_pCut->m_pimBGLayer->m_sWidth;
				m_pCut->m_sDelH = m_pCut->m_pimDst->m_sHeight - m_pCut->m_pimBGLayer->m_sHeight;
				}
			else
				{
				m_pCut->m_sDelW = 0;
				m_pCut->m_sDelH = 0;
				}

			m_rClip.sX = sX + (m_pCut->m_sDelW >> 1);
			m_rClip.sY = sY + (m_pCut->m_sDelH >> 1);
			m_rClip.sW = sW - m_pCut->m_sDelW;
			m_rClip.sH = sH - m_pCut->m_sDelH;

			rspLockBuffer();

			rspRect(RSP_BLACK_INDEX,m_pCut->m_pimDst,0,0,m_pCut->m_pimDst->m_sWidth,m_pCut->m_pimDst->m_sHeight);

			rspUnlockBuffer();

			m_lBaseTime = 0;//rspGetMilliseconds();

			return SUCCESS;
			}

		////////////////////////////////////////////////////////////////////////////
		//		Erase
		////////////////////////////////////////////////////////////////////////////
		void Erase()
			{
			m_sCenX = m_sCenY = m_sRadX = m_sRadY = 
				m_lTimeSpin = m_lCycleTimeX = m_lCycleTimeY = m_lCycleTimeA = 0;
			m_dCenA = m_dRadA = 0.0;
			m_rClip = RRect(0,0,0,0);
			m_siSound = NULL;
			}

		////////////////////////////////////////////////////////////////////////////
		//		CSwirlMe
		////////////////////////////////////////////////////////////////////////////
		CSwirlMe(
			CCutSceneInfo* pCut)
			{
			m_pCut = pCut; // YOU must free the cut scene info - we won't clear it!
			Erase();
			}

		////////////////////////////////////////////////////////////////////////////
		//		~CSwirlMe
		////////////////////////////////////////////////////////////////////////////
		~CSwirlMe()
			{
 			if (m_siSound != 0) AbortSample(m_siSound);
			m_siSound = 0;
			m_pCut = NULL;	// we don't free this - you do!
			}

		////////////////////////////////////////////////////////////////////////////
		//		Update (optional) - from screen buffer only!
		////////////////////////////////////////////////////////////////////////////
		void	Update()
			{
			rspUpdateDisplay(m_rClip.sX,m_rClip.sY,m_rClip.sW,m_rClip.sH);
			}

		////////////////////////////////////////////////////////////////////////////
		//		Clear
		////////////////////////////////////////////////////////////////////////////
		void	Clear()
			{
			//if (m_pimCopy) delete m_pimCopy;
			Erase();
			}
	};


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

static CCutSceneInfo* ms_pCut = NULL;
static CSwirlMe* pSwirl = NULL;


////////////////////////////////////////////////////////////////////////////////
// Function prototypes
////////////////////////////////////////////////////////////////////////////////

static void CutScene_RFileCallback(int32_t lBytes);

////////////////////////////////////////////////////////////////////////////////
//
// Start cutscene.
//
// If 'simple' mode is true, a simple background is displayed with the name
// of the realm file printed on it.
//
// Otherwise, the variables in the specified section of the specified prefs file
// determine what text is printed on top of the background.
//
// If a special effect is desired, CutSceneConfig() must be called, followed
// by repeated calls to CutSceneUpdate().
//
// CutSceneEnd() must be called when the cutscene is no longer needed.
//
////////////////////////////////////////////////////////////////////////////////
extern void CutSceneStart(
	bool bSimple,											// In:  Set to 'true' for simple mode
	const RString* pstrSection,						// In:  Section to use for this realm
	const RString* pstrEntry,							// In:  Entry to use for this realm
	int16_t sBorderX,
	int16_t sBorderY)
	{
	// This is used for more than just paths, so it has a larger size!
	char szText[2048];

	// Init stuff
	ms_pCut = new CCutSceneInfo;
	ms_pCut->m_pimDst = g_pimScreenBuf;

	// Clear screen
	rspLockBuffer();

	rspRect(RSP_BLACK_INDEX, ms_pCut->m_pimDst, 0, 0, ms_pCut->m_pimDst->m_sWidth, ms_pCut->m_pimDst->m_sHeight);

	rspUnlockBuffer();

	rspUpdateDisplay();

	// Open the realm prefs file
	RPrefs prefsRealm;
	if (prefsRealm.Open(FullPathHD(g_GameSettings.m_pszRealmPrefsFile), "rt") != 0)
		{
		TRACE("CutSceneStart(): Error opening prefs file.\n");
		return ;
		}

	//------------------------------------------------------------------------------
	// Get BG
	//------------------------------------------------------------------------------

	// In simple mode, use default bg.  Otherwise, get bg from prefs file.
	if (bSimple)
		{
		strcpy(szText, DEFAULT_BG);
		}
	else
		{
		prefsRealm.GetVal(*pstrSection, "Bg", DEFAULT_BG, szText);
		#ifdef KID_FRIENDLY_OPTION
		if (g_GameSettings.m_sKidMode == TRUE)
		{
			if (strcmp(szText, "res/cutscene/jacketbg.bmp") == 0)
			{
				strcpy(szText, "res/unicorn/jacketbg.bmp");
			}
			else if (strcmp(szText, "res/cutscene/baboon.bmp") == 0 ||
			strcmp(szText, "res/cutscene/bluehead.bmp") == 0 ||
			strcmp(szText, "res/cutscene/dblood.bmp") == 0 ||
			strcmp(szText, "res/cutscene/dholo.bmp") == 0 ||
			strcmp(szText, "res/cutscene/dredeye.bmp") == 0 ||
			strcmp(szText, "res/cutscene/flash.bmp") == 0 ||
			strcmp(szText, "res/cutscene/forest.bmp") == 0 ||
			strcmp(szText, "res/cutscene/glassguy.bmp") == 0 ||
			strcmp(szText, "res/cutscene/guy.bmp") == 0 ||
			strcmp(szText, "res/cutscene/hellbrth.bmp") == 0 ||
			strcmp(szText, "res/cutscene/maskfire.bmp") == 0 ||
			strcmp(szText, "res/cutscene/monster.bmp") == 0 ||
			strcmp(szText, "res/cutscene/pit.bmp") == 0 ||
			strcmp(szText, "res/cutscene/texbeast.bmp") == 0 ||
			strcmp(szText, "res/cutscene/texteeth.bmp") == 0 ||
			strcmp(szText, "res/cutscene/tossle.bmp") == 0 ||
			strcmp(szText, "res/cutscene/war.bmp") == 0)
			{
				strcpy(szText, "res/unicorn/baboon.bmp");
			}
			else if (strcmp(szText, "res/cutscene/abstract.bmp") == 0 ||
			strcmp(szText, "res/cutscene/real2.bmp") == 0 ||
			strcmp(szText, "res/cutscene/ttlmorex.bmp") == 0)
			{
				strcpy(szText, "res/unicorn/abstract.bmp");
			}
			else if (strcmp(szText, "res/cutscene/red2.bmp") == 0)
			{
					strcpy(szText, "res/unicorn/red2.bmp");
			}
		}
		#endif
		if ((strlen(szText) + 1) >= RSP_MAX_PATH)
			{
			TRACE("CutScene(): Bg file name/path too long: '%s'!\n", szText);
			strcpy(szText, DEFAULT_BG);
			}
		}

	// Load bg, but do NOT display yet!
	ms_pCut->m_pimBGLayer = new RImage;
	if ((ms_pCut->m_pimBGLayer->Load(FullPathHD(szText)) == 0) ||
		 (ms_pCut->m_pimBGLayer->Load(FullPathVD(szText)) == 0))
		{
		// Set palette
		ASSERT(ms_pCut->m_pimBGLayer->m_pPalette != NULL);
		ASSERT(ms_pCut->m_pimBGLayer->m_pPalette->m_type == RPal::PDIB);
		rspSetPaletteEntries(
			0, 256, ms_pCut->m_pimBGLayer->m_pPalette->Red(0), 
			ms_pCut->m_pimBGLayer->m_pPalette->Green(0), 
			ms_pCut->m_pimBGLayer->m_pPalette->Blue(0), 
			ms_pCut->m_pimBGLayer->m_pPalette->m_sPalEntrySize);

		rspUpdatePalette();
		}
	else
		{
		TRACE("CutScene(): Error loading bg image: '%s'\n", FullPathVD(szText));
		delete ms_pCut->m_pimBGLayer;
		ms_pCut->m_pimBGLayer	= NULL;
		}

	//------------------------------------------------------------------------------
	// Get tables used for color matching
	//------------------------------------------------------------------------------

	U8	au8Red[256];
	U8	au8Green[256];
	U8	au8Blue[256];
	rspGetPaletteEntries(0, 256, au8Red, au8Green, au8Blue, sizeof(U8));

	//------------------------------------------------------------------------------
	// Generate multialpha
	//------------------------------------------------------------------------------

	ms_pCut->m_pmaAlpha = new RMultiAlpha;

	ms_pCut->m_pmaAlpha->Alloc(1);
	ms_pCut->m_pmaAlpha->CreateLayer(0, 0.5, 10, 236);
	ms_pCut->m_pmaAlpha->m_pAlphaList[0]->ms_SetPalette(ms_pCut->m_pimBGLayer);
	ms_pCut->m_pmaAlpha->m_pAlphaList[0]->CreateAlphaRGB(0.5, 10, 236);
	ms_pCut->m_pmaAlpha->Finish(TRUE);

	//------------------------------------------------------------------------------
	// Get font
	//------------------------------------------------------------------------------

	// Try to load font, but if it fails, use the already-loaded game font
	ms_pCut->m_bDeleteFont = true;
	ms_pCut->m_pFont = new RFont;
	if (ms_pCut->m_pFont->Load(FullPathVD(FONT_FILE)) != SUCCESS)
		{
		TRACE("CutScene(): Error loading font: '%s'!\n", FullPathVD(FONT_FILE));
		delete ms_pCut->m_pFont;
		ms_pCut->m_pFont = &g_fontBig; // system font
		ms_pCut->m_bDeleteFont = false; // don't try to free it!
		}

	// Find closest matches for the desired colors
	ms_pCut->m_ucForeText = rspMatchColorRGB(
		FONT_FORE_R, FONT_FORE_G, FONT_FORE_B, 10, 236, au8Red, au8Green, au8Blue, sizeof(U8));
	ms_pCut->m_ucShadowText = rspMatchColorRGB(
		FONT_SHAD_R, FONT_SHAD_G, FONT_SHAD_B, 10, 236, au8Red, au8Green, au8Blue, sizeof(U8));

	// Setup print
	RPrint print;


	//------------------------------------------------------------------------------
	// Get title
	//------------------------------------------------------------------------------

	// In simple mode, use default title.  Otherwise, get title from prefs file.
	if (bSimple)
		{
		strcpy(ms_pCut->m_szTitle, DEFAULT_TITLE);
		}
	else
		{
		prefsRealm.GetVal(*pstrSection, "Title", DEFAULT_TITLE, ms_pCut->m_szTitle);
		if ((strlen(ms_pCut->m_szTitle) + 1) >= sizeof(ms_pCut->m_szTitle))
			{
			TRACE("CutScene(): Title too long: '%s'!\n", ms_pCut->m_szTitle);
			strcpy(ms_pCut->m_szTitle, DEFAULT_TITLE);
			}
		}

 	//------------------------------------------------------------------------------
	// Get music
	//------------------------------------------------------------------------------

	// In simple mode, use the default music, otherwise get the music from prefs file
	if (bSimple)
		{
		strcpy(ms_pCut->m_szMusic, DEFAULT_MUSIC);
		}
	else
		{
		prefsRealm.GetVal(*pstrSection, "Music", DEFAULT_MUSIC, ms_pCut->m_szMusic);
		if ((strlen(ms_pCut->m_szMusic) + 1) >= sizeof(ms_pCut->m_szMusic))
			{
			TRACE("CutScene(): Music filename too long: '%s'!\n", ms_pCut->m_szMusic);
			strcpy(ms_pCut->m_szMusic, DEFAULT_MUSIC);
			}
		}

	// Set the resource name for the sample
	ms_pCut->m_musicID.pszId			= ms_pCut->m_szMusic;
	ms_pCut->m_musicID.usDescFlags	= SMDF_NO_DESCRIPT;

	//------------------------------------------------------------------------------
	//	Create the Text overlay layer (GENLOCK!)
	//------------------------------------------------------------------------------
	
	ms_pCut->m_pimTextLayer = new RImage;
	ms_pCut->m_pimTextLayer->CreateImage(ms_pCut->m_pimDst->m_sWidth,ms_pCut->m_pimDst->m_sHeight,RImage::BMP8);

	// Create text layer
	print.SetDestination(ms_pCut->m_pimTextLayer);
	print.SetFont(48, ms_pCut->m_pFont);
	print.SetColor(ms_pCut->m_ucForeText, 0, ms_pCut->m_ucShadowText);
	print.SetEffectAbs(RPrint::SHADOW_X, 2);
	print.SetEffectAbs(RPrint::SHADOW_Y, 2);
	print.print(((ms_pCut->m_pimTextLayer->m_sWidth - print.GetWidth(ms_pCut->m_szTitle)) / 2),
		NAME_Y, ms_pCut->m_szTitle);

	// Get text to be displayed
	if (bSimple)
		{
		strcpy(ms_pCut->m_szText, DEFAULT_TEXT);
		}
	else
		{
		prefsRealm.GetVal(*pstrSection, "Text", DEFAULT_TEXT, ms_pCut->m_szText);
		if ((strlen(ms_pCut->m_szText) + 1) >= sizeof(ms_pCut->m_szText))
			{
			TRACE("CutScene(): Text too long: '%s'!\n", ms_pCut->m_szText);
			strcpy(ms_pCut->m_szText, DEFAULT_TEXT);
			}
		}

	// Create cropping rectangle
	// Try to compensate by the way the POSTAL cut scene assets were hosed

	RRect	rCrop;
	if (ms_pCut->m_pimBGLayer)
		{
		rCrop = RRect(
			BG_X + sBorderX,
			BG_Y + sBorderY + 40, // there were horizontal strips implanted
			ms_pCut->m_pimBGLayer->m_sWidth - (sBorderX * 2),
			ms_pCut->m_pimBGLayer->m_sHeight - (sBorderY * 2) - 80);
		}
	else
		{
		rCrop.sX	= 0;
		rCrop.sY	= 0;
		rCrop.sW	= g_pimScreenBuf->m_sWidth - (sBorderX * 2);
		rCrop.sH	= g_pimScreenBuf->m_sHeight - (sBorderY * 2) - 80;
		}

	// Create text layer
	print.SetFont(28, ms_pCut->m_pFont);
	print.SetColor(ms_pCut->m_ucForeText, 0, ms_pCut->m_ucShadowText);
	print.SetEffectAbs(RPrint::SHADOW_X, 1);
	print.SetEffectAbs(RPrint::SHADOW_Y, 1);
	print.SetWordWrap(TRUE);
	print.SetColumn(rCrop.sX + MARGIN, rCrop.sY, rCrop.sW - (MARGIN*2), rCrop.sH);
	print.print(0, TEXT_Y, ms_pCut->m_szText);

	ms_pCut->m_pimTextLayer->Convert(RImage::FSPR8);

	//------------------------------------------------------------------------------
	// Update the actual screen now that everything is ready
	//------------------------------------------------------------------------------

	// We must lock the composite buffer before reading/writing it.
	rspLockBuffer();

	// Clear dst to black
	rspRect(
		RSP_BLACK_INDEX,
		ms_pCut->m_pimDst,
		0, 0,
		ms_pCut->m_pimDst->m_sWidth, ms_pCut->m_pimDst->m_sHeight);

	if (ms_pCut->m_pimBGLayer)
		{
		// Copy bg image to dst, cropping as we go
		rspBlit(
			ms_pCut->m_pimBGLayer,
			ms_pCut->m_pimDst,
			0, 0,
			BG_X, BG_Y,
			ms_pCut->m_pimBGLayer->m_sWidth, ms_pCut->m_pimBGLayer->m_sHeight,
			&rCrop);

		RRect	rcBGClipper(
			0,			
			0,	
			ms_pCut->m_pimBGLayer->m_sWidth,
			ms_pCut->m_pimBGLayer->m_sHeight);

		// Copy progress bar area of bg image to dst with SOURCE clipping
		// just in case image is too small.
		rspBlit(
			ms_pCut->m_pimBGLayer,								// Src.
			ms_pCut->m_pimDst,									// Dst.
			PROGRESS_BOX_X, PROGRESS_BOX_Y,					// Src.
			PROGRESS_BOX_X, PROGRESS_BOX_Y,					// Dst.
			PROGRESS_BOX_WIDTH, PROGRESS_BOX_HEIGHT,		// Both.
			NULL,														// Dst.
			&rcBGClipper);											// Src.
		}

	// Copy text image to dst, cropping as we go
	rspBlit(
		ms_pCut->m_pimTextLayer,
		ms_pCut->m_pimDst,
		0, 0,
		&rCrop);

	// Release composite buffer now that we are done.
	rspUnlockBuffer();

	// Update display
	rspUpdateDisplay();

	//------------------------------------------------------------------------------
	// Close prefs file.
	//------------------------------------------------------------------------------
	prefsRealm.Close();

	//------------------------------------------------------------------------------
	// Setup stuff for the progress bar
	//------------------------------------------------------------------------------

	// Hook the RFile callback that tells us how much data is about to be read/written
	RFile::ms_criticall = CutScene_RFileCallback;

	// Reset various stuff
	ms_pCut->m_lBytesSoFar = 0;
	ms_pCut->m_lTotalBytes = 12000000;
	ms_pCut->m_lTimeToUpdate = rspGetMilliseconds();
	ms_pCut->m_sLastDistance = 0;

	// Find good blood color
	ms_pCut->m_u8BloodColor = rspMatchColorRGB(
		BLOOD_FORE_R, BLOOD_FORE_G, BLOOD_FORE_B, 1, 254, au8Red, au8Green, au8Blue, sizeof(U8));
	}


////////////////////////////////////////////////////////////////////////////
//
// Configure cutscene effect
//
////////////////////////////////////////////////////////////////////////////
extern int16_t CutSceneConfig(
	int32_t lTimeSpin,
	int16_t sMinX,int16_t sMaxX,int32_t lTimeX,
	int16_t sMinY,int16_t sMaxY,int32_t lTimeY,
	double dMinA,double dMaxA,int32_t lTimeA,
	int16_t sX,int16_t sY,int16_t sW,int16_t sH)
	{
	ASSERT(ms_pCut);
	pSwirl = new CSwirlMe(ms_pCut);

	if (ms_pCut->m_pimBGLayer)
		{
		// Erase progress bar region from BG layer and dst and display
		// Note: We use the color index in the upper left corner of the BG layer
		// to erase this area.  We can't use the expected RSP_BLACK_INDEX because
		// on the mac, it creates problems with the multialpha stuff.  We assume
		// the pixel in the upper left corner is black, but it's a DIFFERENT black
		// which is safe to use with the multialpha stuff.
		rspRect(
			*(ms_pCut->m_pimBGLayer->m_pData),
			ms_pCut->m_pimBGLayer,
			PROGRESS_BOX_X, PROGRESS_BOX_Y,
			PROGRESS_BOX_WIDTH, PROGRESS_BOX_HEIGHT);

		RRect	rcBGClipper(
			0,
			0,
			ms_pCut->m_pimBGLayer->m_sWidth,
			ms_pCut->m_pimBGLayer->m_sHeight);

		rspLockBuffer();

		// Copy progress bar area of bg image to dst with SOURCE clipping
		// just in case image is too small.
		rspBlit(
			ms_pCut->m_pimBGLayer,								// Src.
			ms_pCut->m_pimDst,									// Dst.
			PROGRESS_BOX_X, PROGRESS_BOX_Y,					// Src.
			PROGRESS_BOX_X, PROGRESS_BOX_Y,					// Dst.
			PROGRESS_BOX_WIDTH, PROGRESS_BOX_HEIGHT,		// Both.
			NULL,														// Dst.
			&rcBGClipper);											// Src.

		rspUnlockBuffer();

		rspUpdateDisplay(PROGRESS_BOX_X, PROGRESS_BOX_Y, PROGRESS_BOX_WIDTH, PROGRESS_BOX_HEIGHT);
		}

	// Clear RFile hook so nothing gets drawn after this
	RFile::ms_criticall = 0;

	return pSwirl->Configure(
		lTimeSpin,
		sMinX, sMaxX, lTimeX,
		sMinY, sMaxY, lTimeY,
		dMinA, dMaxA, lTimeA,
		sX, sY, sW, sH);
	}


////////////////////////////////////////////////////////////////////////////
//
// Update cutscene effect
//
////////////////////////////////////////////////////////////////////////////
extern void CutSceneUpdate(void)
	{
	ASSERT(ms_pCut);
	ASSERT(pSwirl);

	pSwirl->MakeFrame(&(ms_pCut->m_musicID));
	pSwirl->Update();
	}


////////////////////////////////////////////////////////////////////////////////
//
// Clean up after the cutscene.  It is safe to call this multiple times.
//
////////////////////////////////////////////////////////////////////////////////
extern void CutSceneEnd(void)
	{
	delete ms_pCut;
	ms_pCut = 0;

	delete pSwirl;
	pSwirl = 0;

	// Clear RFile hook
	RFile::ms_criticall = 0;
	}


////////////////////////////////////////////////////////////////////////////////
//
// Our RFile callback
//
////////////////////////////////////////////////////////////////////////////////
static void CutScene_RFileCallback(int32_t lBytes)
	{
	static int16_t asWavyY[] =
		{
		0, 1, 2, 0, -2, -1, 1, 0, -1, 0, 1, 2, 1, 0,
		1, 0, -1, -2, -1, -2, 0, 1, 2, 0, -2, -1, 0, 0
		};

	if (ms_pCut)
		{
		if (ms_pCut->m_pimBGLayer && ms_pCut->m_pmaAlpha)
			{
			if (ms_pCut->m_pimBGLayer->m_type != RImage::NOT_SUPPORTED)
				{
				// Update bytes so far
				ms_pCut->m_lBytesSoFar += lBytes;

				// Check if time for an update
				int32_t lNow = rspGetMilliseconds();
				if ((lNow - ms_pCut->m_lTimeToUpdate) > PROGRESS_BAR_UPDATE_TIME)
					{
					// Get percentage that's been loaded so far (result is from 0 to 1)
					double dPercentage = (double)ms_pCut->m_lBytesSoFar / (double)ms_pCut->m_lTotalBytes;
					if (dPercentage > 1.0)
						dPercentage = 1.0;
					int16_t sDistance = (int16_t)((double)PROGRESS_BAR_WIDTH * dPercentage);

					int16_t sBaseY = asWavyY[(int16_t)(dPercentage * (NUM_ELEMENTS(asWavyY) - 1) )];
						 
					// Draw from previous distance to current distance in case a big jump occurred
					for (int16_t sBaseX = ms_pCut->m_sLastDistance; sBaseX <= sDistance; sBaseX++)
						{
						// Draw a few pixels at a time to get a more solid look
						for (int16_t i = 0; i < PROGRESS_BAR_DENSITY; i++)
							{
							// Choose random location nearby the specified location
							int16_t sX = PROGRESS_BAR_START_X + sBaseX + RandomPlusMinus(PROGRESS_BAR_RANDOM_X);
							int16_t sY = PROGRESS_BAR_START_Y + sBaseY + RandomPlusMinus(PROGRESS_BAR_RANDOM_Y);

							// Draw an alpha'd pixel at this location
							U8 u8dst = *(ms_pCut->m_pimBGLayer->m_pData + (sY * ms_pCut->m_pimBGLayer->m_lPitch) + sX);
							rspPlot(
								rspBlendColor(150, ms_pCut->m_pmaAlpha, ms_pCut->m_u8BloodColor, u8dst),
								ms_pCut->m_pimBGLayer,
								sX,
								sY);
							}
						}

					// Save for next time
					ms_pCut->m_sLastDistance = sDistance;
					
					// Update progress bar area

					RRect	rcBGClipper(
						0,
						0,
						ms_pCut->m_pimBGLayer->m_sWidth,
						ms_pCut->m_pimBGLayer->m_sHeight);

					rspLockBuffer();

					// Copy progress bar area of bg image to dst with SOURCE clipping
					// just in case image is too small.
					rspBlit(
						ms_pCut->m_pimBGLayer,								// Src.
						ms_pCut->m_pimDst,									// Dst.
						PROGRESS_BOX_X, PROGRESS_BOX_Y,					// Src.
						PROGRESS_BOX_X, PROGRESS_BOX_Y,					// Dst.
						PROGRESS_BOX_WIDTH, PROGRESS_BOX_HEIGHT,		// Both.
						NULL,														// Dst.
						&rcBGClipper);											// Src.

					rspUnlockBuffer();

					rspUpdateDisplay(PROGRESS_BOX_X, PROGRESS_BOX_Y, PROGRESS_BOX_WIDTH, PROGRESS_BOX_HEIGHT);
					
					// Do an update
					UpdateSystem();

					// Save new time
					ms_pCut->m_lTimeToUpdate = lNow;
					}
				}
			}
		}
	}

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