#include "driver2.h"
#include "cutscene.h"

#include "mission.h"
#include "cars.h"
#include "players.h"
#include "replays.h"
#include "gamesnd.h"
#include "glaunch.h"
#include "civ_ai.h"
#include "pedest.h"
#include "pres.h"
#include "system.h"
#include "camera.h"
#include "main.h"
#include "mc_snd.h"
#include "handling.h"
#include "event.h"
#include "denting.h"
#include "pause.h"
#include "overmap.h"
#include "director.h"
#include "xaplay.h"
#include "overlay.h"

struct CUTSCENE_BUFFER
{
	int numResident;
	u_char residentCutscenes[4];
	char(*residentPointers[4]);

	char* buffer;
	char* currentPointer;

	int bytesFree;
	int reservedSize;
	// char buffer[32*1024];				// was 8192, but we have some free mem now even for PSX. Using malloc.
};

int gSkipInGameCutscene = 0;

int gInGameCutsceneActive = 0;
int gInGameCutsceneDelay = 0;
int gInGameChaseActive = 0;
int gInGameCutsceneID = -1;
int gCutsceneAtEnd = 0;
int gThePlayerCar = -1;

static int CutsceneStreamIndex = 0;
int CutsceneFrameCnt = 0;
int NumCutsceneStreams = 0;
static int gHaveInGameCutscene = 0;
static int gCSDestroyPlayer = 0;
static int PreLoadedCutscene = -1;
static char *CutsceneReplayStart = NULL;
static int CutsceneInReplayBuffer = 0;
static int CutsceneLength = 0;
static int BlackBorderHeight = 0;
int JustReturnedFromCutscene = 0;
int CutsceneEventTrigger = 0;

CUTSCENE_BUFFER CutsceneBuffer = { 0 };

// [A]
CUTSCENE_HEADER CutsceneHeader;
CUTSCENE_HEADER ChaseHeader;
int gReChaseAvailable = 0;

static PLAYBACKCAMERA *CutLastChange;
static PLAYBACKCAMERA *CutNextChange;
PLAYBACKCAMERA *CutsceneCamera = NULL;

static int CutsceneCameraOffset = 0;

int LoadInGameCutscene(int subindex);
int TriggerInGameCutsceneSystem(int cutscene);
void SetNullPlayer(int plr);
void SetNullPlayerDontKill(int plr);
void DestroyPlayer(int plr, int fully);
void FindNextCutChange(int cameracnt);
int LoadCutsceneToReplayBuffer(int residentCutscene);
int LoadCutsceneToBuffer(int subindex);
void ShowCutsceneError();
int LoadCutsceneInformation(int cutscene);
void FreeCutsceneBuffer();
int IsCutsceneResident(int cutscene);


#ifndef PSX
char gUserReplayFolderList[MAX_USER_REPLAYS][48];
int gNumUserChases = 0;
int gUserChaseLoaded = -1;

// [A] user replay folders initialization
void InitUserReplays(const char* str)
{
	int quit;
	char* ptr;
	char* strStart;
	gNumUserChases = 0;

	if (!str)
		return;

	ptr = (char*)str;
	strStart = NULL;
	memset(gUserReplayFolderList, 0, sizeof(gUserReplayFolderList));

	quit = 0;

	while(true)
	{
		if (strStart == NULL)
			strStart = ptr;

		// if we're encountered string end go on
		if(*ptr == ',' || *ptr == ' ' || *ptr == '\0')
		{
			if (*ptr == '\0')
				quit = 1;

			*ptr = '\0';
			strcpy(gUserReplayFolderList[gNumUserChases++], strStart);
			strStart = NULL;
		}

		ptr++;

		if (quit)
			break;
	}
}


#endif


// [D] [T]
void InitInGameCutsceneVariables(void)
{
	CutsceneStreamIndex = 0;
	CutsceneFrameCnt = 0;
	NumCutsceneStreams = 0;
	gHaveInGameCutscene = 0;
	gInGameChaseActive = 0;
	gInGameCutsceneActive = 0;
	gCSDestroyPlayer = 0;
	PreLoadedCutscene = -1;
	CutsceneReplayStart = NULL;
	CutsceneCamera = NULL;
	CutsceneInReplayBuffer = 0;
	CutsceneEventTrigger = 0;
	CutsceneLength = 0;
	BlackBorderHeight = 0;
	gInGameCutsceneDelay = 0;
	gInGameCutsceneID = -1;
	gCutsceneAtEnd = 0;
	JustReturnedFromCutscene = 0;

	gSkipInGameCutscene = 0;

#ifndef PSX
	gUserChaseLoaded = -1;
#endif

	FreeCutsceneBuffer();
}

// [D] [T]
void HandleInGameCutscene(void)
{
	if (gInGameCutsceneDelay != 0) 
	{
		BlackBorderHeight = gInGameCutsceneDelay;
		gInGameCutsceneDelay++;

		if (gInGameCutsceneDelay > 27)
		{
			TriggerInGameCutscene(gInGameCutsceneID);
			gInGameCutsceneDelay = 0;
			BlackBorderHeight = 28;
		}
	}

	if (gHaveInGameCutscene == 0)
		return;

	if (pauseflag != 0)
		return;

	if (CameraCnt < 2)
		BlackBorderHeight = 28;

	if (CutsceneLength-28 < CutsceneFrameCnt) 
	{
		gSkipInGameCutscene = 0;
		
		if (BlackBorderHeight > 0)
			BlackBorderHeight--;
	}
	else 
	{
		if (BlackBorderHeight < 28)
			BlackBorderHeight++;
	}

	CutsceneFrameCnt++;

	if (gSkipInGameCutscene)
	{
		StopXA();
		UnprepareXA();
	}

	if (CutsceneFrameCnt == CutsceneLength) 
	{
		gSkipInGameCutscene = 0;
		gHaveInGameCutscene = 0;

		if (gInGameCutsceneActive != 0)
			JustReturnedFromCutscene = 1;

		ReleaseInGameCutscene();
		InitOverheadMap();
	}
}

// [D] [T]
void DrawInGameCutscene(void)
{
	TILE *tile;

#ifndef PSX
	PrintXASubtitles();
#endif
	
	if (gInGameCutsceneActive == 0 && gInGameCutsceneDelay == 0)
	{
#ifndef PSX
		if(gInGameChaseActive && gUserChaseLoaded != -1 && (CameraCnt - frameStart) < 200)
		{
			// [A] print user chaser name on screen
			char tempStr[80];

			sprintf(tempStr, "%s %s", G_LTXT(GTXT_GetawayIs), gUserReplayFolderList[gUserChaseLoaded]);

			SetTextColour(128, 128, 64);
			PrintString(tempStr, gOverlayXPos, 230);
		}
#endif
		return;
	}

	tile = (TILE *)current->primptr;
	SetTile(tile);
	tile->r0 = 0;
	tile->g0 = 0;
	tile->b0 = 0;

#ifdef PSX
	tile->x0 = 0;
	tile->w = 320;
#else
	tile->x0 = -500;
	tile->w = 1200;
#endif

	tile->y0 = 0;
	tile->h = BlackBorderHeight;

	addPrim(current->ot, tile);
	tile++;

	SetTile(tile);
	tile->r0 = 0;
	tile->g0 = 0;
	tile->b0 = 0;

#ifdef PSX
	tile->x0 = 0;
	tile->w = 320;
#else
	tile->x0 = -500;
	tile->w = 1200;
#endif

	tile->y0 = SCREEN_H - BlackBorderHeight;
	tile->h = BlackBorderHeight;

	addPrim(current->ot, tile);

	current->primptr += sizeof(TILE) * 2;
}

// [D] [T]
void TriggerChase(int *car, int cutscene)
{
	int *inform;
	int i;
	int length;

	inform = car_data[*car].inform;
	car_data[*car].inform = NULL;

	gInGameChaseActive = TriggerInGameCutsceneSystem(gRandomChase);

	if (gInGameChaseActive)
	{
		length = 0;

		// find maximum length of cutscene
		for (i = CutsceneStreamIndex; i < NumReplayStreams; i++)
		{
			if (ReplayStreams[i].length > length)
				length = ReplayStreams[i].length;
		}

		switch (gCurrentMissionNumber)
		{
			case 2:
			case 4:
			case 6:
			case 10:
			case 18:
				Mission.timer[0].count = (15000 + (length / 30) * 3000);
				break;
			default:
				Mission.timer[0].count = (length / 30) * 3000 - 15000;
		}

		Mission.timer[0].flags = TIMER_FLAG_ACTIVE;

		*car = CutsceneStreamIndex;
		player[0].targetCarId = CutsceneStreamIndex;

		InitLeadHorn();
	}

	car_data[*car].inform = inform;
}

static int SavedCameraView = 0;
static int SavedWorldCentreCarId = 0;
static VECTOR *SavedSpoolXZ = NULL;
static int SavedCameraCarId = 0;
static int SavedCameraAngle = 0;

// [D] [T]
void TriggerInGameCutscene(int cutscene)
{
	if (IsCutsceneResident(cutscene) == 0)
		return;

	gInGameCutsceneID = cutscene;

	if (CameraCnt < 3 || gInGameCutsceneDelay > 27)
	{
		SavedCameraCarId = player[0].cameraCarId;
		SavedCameraView = cameraview;
		SavedWorldCentreCarId = player[0].worldCentreCarId;
		SavedCameraAngle = gCameraAngle;
		SavedSpoolXZ = player[0].spoolXZ;

		gInGameCutsceneActive = TriggerInGameCutsceneSystem(cutscene);

		if (gInGameCutsceneActive) 
		{
			TerminateSkidding(0);
			TerminateSkidding(1);

			gStopPadReads = 1;
			scr_z = 256;

			if (gCSDestroyPlayer) 
			{
				DestroyPlayer(0, 1);
			}
		}
	}
	else 
	{
		gCutsceneAtEnd = 1;
		gInGameCutsceneDelay = gInGameCutsceneDelay + 1;
	}
}

// [A] Function detects user chases and re-chases
int SelectCutsceneFile(char* filename, int init, int subindex)
{
	filename[0] = '\0';

	if (init)
	{
		// try load replacement bundle
#ifndef PSX
		int userId = -1;

		// [A] REDRIVER2 PC - custom user chases
		if (gNumUserChases)
		{
			userId = rand() % (gNumUserChases + 1);

			// if random decides to have no user chase - get og or replacement one
			if (userId == gNumUserChases)
				userId = -1;
		}

		// try loading user chase
		if (userId != -1)
			sprintf(filename, "REPLAYS\\UserChases\\%s\\CUT%d_N.R", (char*)gUserReplayFolderList[userId], gCurrentMissionNumber);

		if (FileExists(filename))
		{
			gUserChaseLoaded = userId;
			gReChaseAvailable = 0;
		}
		else
#endif
		{
			sprintf(filename, "REPLAYS\\ReChases\\CUT%d_N.R", gCurrentMissionNumber);
			gReChaseAvailable = FileExists(filename);
		}
	}

	if (subindex >= 2)
	{
		if (gReChaseAvailable == 1)
		{
			sprintf(filename, "REPLAYS\\ReChases\\CUT%d_N.R", gCurrentMissionNumber);
		}
#ifndef PSX
		else if (gUserChaseLoaded != -1)
		{
			sprintf(filename, "REPLAYS\\UserChases\\%s\\CUT%d_N.R", (char*)gUserReplayFolderList[gUserChaseLoaded], gCurrentMissionNumber);
		}
#endif
	}
	else
	{
		if (gCurrentMissionNumber < 21)
			sprintf(filename, "REPLAYS\\CUT%d.R", gCurrentMissionNumber);
		else
			sprintf(filename, "REPLAYS\\A\\CUT%d.R", gCurrentMissionNumber);
	}

	return FileExists(filename);
}

// [D] [T]
int CalcInGameCutsceneSize(void)
{
	int i, maxSize;
	char filename[64];

	if (NewLevel)
	{
		ClearMem((char*)&CutsceneHeader, sizeof(CUTSCENE_HEADER));
		ClearMem((char*)&ChaseHeader, sizeof(CUTSCENE_HEADER));
		
		// init user/re-chases and load cutscene file header
		if(SelectCutsceneFile(filename, 1, 0))
			LoadfileSeg(filename, (char*)&CutsceneHeader, 0, sizeof(CUTSCENE_HEADER));

		// load re-chase file header
		if(SelectCutsceneFile(filename, 0, 2))
			LoadfileSeg(filename, (char*)&ChaseHeader, 0, sizeof(CUTSCENE_HEADER));

		maxSize = 0;
		for (i = 2; i < 15; i++)
		{
			if (ChaseHeader.data[i].size > maxSize)
				maxSize = ChaseHeader.data[i].size;
		}

		maxSize += MAX(CutsceneHeader.data[0].size, CutsceneHeader.data[1].size);

		CutsceneBuffer.reservedSize = maxSize;
		CutsceneBuffer.buffer = D_MALLOC(CutsceneBuffer.reservedSize);
	}

	ClearMem(CutsceneBuffer.buffer, CutsceneBuffer.reservedSize);

	return CutsceneHeader.maxsize;
}

// [D] [T]
void ReleaseInGameCutscene(void)
{
	int i;

	if (gInGameChaseActive && Mission.ChaseTarget) 
	{
		player[1].padid = -128;
	}

	if (gInGameCutsceneActive != 0)
	{
		PingOutAllCivCarsAndCopCars();
		InitCivCars();

		for (i = CutsceneStreamIndex; i < NumReplayStreams; i++)
		{
			if (PlayerStartInfo[i]->flags & 4)
			{
				memcpy((u_char*)&player[0], (u_char*)&player[i], sizeof(PLAYER));

				if (player[0].playerType == 2)
				{
					player[0].pPed->padId = 0;
					SavedWorldCentreCarId = -1;
					SavedSpoolXZ = (VECTOR *)&player[0].pPed->position;
				}
				else
				{
					Swap2Cars(player[0].playerCarId, 0);
					SavedWorldCentreCarId = player[0].playerCarId;

					SavedSpoolXZ = (VECTOR *)car_data[SavedWorldCentreCarId].hd.where.t;

					car_data[SavedWorldCentreCarId].ai.padid = &player[0].padid;
				}

				SavedCameraCarId = SavedWorldCentreCarId;

				DestroyPlayer(i, 0);
			}
			else 
			{
				DestroyPlayer(i, 1);
			}
		}

		gThePlayerCar = -1;
		player[0].targetCarId = -1;

		DestroyCivPedestrians();
		AdjustPlayerCarVolume();

		i = player[0].playerCarId;

		player[0].padid = 0;
		player[0].worldCentreCarId = SavedWorldCentreCarId;
		player[0].spoolXZ = SavedSpoolXZ;
		player[0].cameraCarId = SavedCameraCarId;

		if (i != -1) 
		{
			car_data[i].ai.padid = &player[0].padid;
			car_data[i].controlType = CONTROL_TYPE_PLAYER;
		}

		cameraview = SavedCameraView;
		gCameraAngle = SavedCameraAngle;

		InitCamera(&player[0]);

		player[0].cameraAngle = 0;
		player[0].cameraDist = 1000;

		CutsceneEventTrigger = 0;
	}

	if (CutsceneReplayStart != NULL) 
		replayptr = CutsceneReplayStart;

	NumReplayStreams -= NumCutsceneStreams;
	NumCutsceneStreams = 0;

	CutsceneReplayStart = NULL;
	CutsceneStreamIndex = 0;
	
	PreLoadedCutscene = -1;
	gHaveInGameCutscene = 0;
	gCSDestroyPlayer = 0;
	CutsceneLength = 0;
	BlackBorderHeight = 0;
	CutsceneCamera = NULL;
	gInGameChaseActive = 0;
	gInGameCutsceneActive = 0;
	CutsceneFrameCnt = 0;
	gInGameCutsceneDelay = 0;
	gStopPadReads = 0;

	gCameraOffset.vx = 0;
	gCameraOffset.vy = 0;
	gCameraOffset.vz = 0;
	gCameraMaxDistance = 0;

	xa_timeout = 120;
}

// [D] [T]
int PreLoadInGameCutscene(int chase)
{
	return LoadInGameCutscene(chase) != 0;
}

// [D] [T]
int CutsceneCameraChange(int cameracnt)
{
	cameracnt -= CutsceneCameraOffset;

	if (CutNextChange->FrameCnt != cameracnt) 
	{
		if (CutNextChange->FrameCnt > cameracnt) 
		{
			IsMovingCamera(CutLastChange, CutNextChange, cameracnt);
			return 0;
		}

		if (CutNextChange->next == 254) 
			return 0;
	}

	if (CutLastChange != CutNextChange && CutNextChange->angle.pad > -1)
		CutNextChange->angle.pad += CutsceneStreamIndex;

	SetPlaybackCamera(CutNextChange);

	if (cameracnt > -1) 
	{
		InvalidCamera(player[0].cameraCarId);

		CutLastChange = CutNextChange;
		FindNextCutChange(cameracnt + 1);
	}

	return 1;
}

// [D] [T]
int LoadInGameCutscene(int subindex)
{
	if (CutsceneInReplayBuffer)
		return LoadCutsceneToBuffer(subindex);

	// first cutscene (intro) always goes to replay buffer
	if (LoadCutsceneToBuffer(subindex))
	{
		if (LoadCutsceneToReplayBuffer(0))
		{
			CutsceneInReplayBuffer = 1;
			PreLoadedCutscene = subindex;
		}

		FreeCutsceneBuffer();
		return 1;
	}

	return 0;
}

// [D] [T]
int TriggerInGameCutsceneSystem(int cutscene)
{
	static char padid[8];

	int slot;
	CAR_DATA *cp;
	REPLAY_STREAM *stream;
	int player_id;
	int bDamageOverride;

	bDamageOverride = 0;

	if(cutscene > -1)
	{
		gHaveInGameCutscene = LoadCutsceneInformation(cutscene);

		if (gHaveInGameCutscene != 0) 
		{
			PingOutAllCivCarsAndCopCars();
			InitCivCars();
			DestroyCivPedestrians();

			if (CutsceneStreamIndex <= player[0].playerCarId)
			{
				Swap2Cars(player[0].playerCarId, 0);
				SavedCameraCarId = player[0].cameraCarId;
				SavedWorldCentreCarId = player[0].playerCarId;
				SavedSpoolXZ = player[0].spoolXZ;
			}

			if (CutsceneEventTrigger != 0) 
			{
				TriggerEvent(CutsceneEventTrigger);
			}

			gThePlayerCar = -1;

			if (CutsceneStreamIndex < NumReplayStreams)
			{
				stream = ReplayStreams + CutsceneStreamIndex;
				player_id = CutsceneStreamIndex;

				do {
					PlayerStartInfo[player_id] = &stream->SourceType;

					if ((stream->SourceType.flags & 4) != 0)
					{
						gCSDestroyPlayer = 1;

						if (stream->SourceType.type == 1)
						{
							gThePlayerCar = player_id;

							if (gCutsceneAtEnd != 0 && player[0].playerType == 1)
							{
								stream->SourceType.palette = car_data[player[0].playerCarId].ap.palette;
								stream->SourceType.model = MissionHeader->residentModels[car_data[player[0].playerCarId].ap.model];

								bDamageOverride = 1;
								gCutsceneAtEnd = 0;
							}
						}
					}

					padid[player_id] = -player_id;
					gStartOnFoot = stream->SourceType.type == 2;

					if (gStartOnFoot || (stream->SourceType.flags & 1) == 0)
					{
						TerminateSkidding(player_id);

						cp = &car_data[player_id];

						InitPlayer(&player[player_id], cp,
							stream->SourceType.controlType, 
							stream->SourceType.rotation,
							(LONGVECTOR4* )&stream->SourceType.position,
							stream->SourceType.model,
							stream->SourceType.palette,
							&padid[player_id]);

						if (bDamageOverride) 
						{
							slot = player[0].playerCarId;

							cp->ap.needsDenting = 1;
							cp->ap.damage[0] = car_data[slot].ap.damage[0];
							cp->ap.damage[1] = car_data[slot].ap.damage[1];
							cp->ap.damage[2] = car_data[slot].ap.damage[2];
							cp->ap.damage[3] = car_data[slot].ap.damage[3];
							cp->ap.damage[4] = car_data[slot].ap.damage[4];
							cp->ap.damage[5] = car_data[slot].ap.damage[5];

							bDamageOverride = 0;

							cp->totalDamage = car_data[slot].totalDamage;
							DentCar(cp);
						}
					}
					else 
					{
						slot = CreateStationaryCivCar(stream->SourceType.rotation, 0, 1024, 
							(LONGVECTOR4* )&stream->SourceType.position,
							stream->SourceType.model, 
							stream->SourceType.palette, 0);

						if (slot != -1) 
						{
							player[player_id].playerCarId = slot;
							SetNullPlayer(player_id);
						}
					}

					if (player_id == CutsceneStreamIndex)
					{
						if (gStartOnFoot == 0) 
						{
							player[0].spoolXZ = (VECTOR *)car_data[player_id].hd.where.t;
							player[0].worldCentreCarId = CutsceneStreamIndex;
						}
						else 
						{
							player[0].worldCentreCarId = -1;
							player[0].spoolXZ = (VECTOR *)&player[player_id].pPed->position;
						}
					}

					if (CutsceneLength < stream->length)
						CutsceneLength = stream->length;

					stream++;
					player_id++;

				} while (player_id < NumReplayStreams);
			}

			CutsceneCameraOffset = CameraCnt + 1;
			CutNextChange = CutsceneCamera;
			CutLastChange = NULL;

			return 1;
		}
	}



	ShowCutsceneError();
	return 0;
}

// [D] [T]
void SetNullPlayer(int plr)
{
	int carId = player[plr].playerCarId;

	if (carId != -1) 
	{
		car_data[carId].controlType = CONTROL_TYPE_CIV_AI;
		car_data[carId].ai.c.thrustState = 3;
		car_data[carId].ai.c.ctrlState = 7;
		car_data[carId].ai.c.ctrlNode = NULL;

		player[plr].playerCarId = -1;
	}

	player[plr].playerType = 3;
}

// [D] [T]
void SetNullPlayerDontKill(int plr)
{
	int carId = player[plr].playerCarId;

	if (carId != -1)
	{
		car_data[carId].controlType = CONTROL_TYPE_CUTSCENE;
		car_data[carId].ai.c.thrustState = 3;
		car_data[carId].ai.c.ctrlState = 7;
		car_data[carId].ai.c.ctrlNode = NULL;

		player[plr].playerCarId = -1;
	}

	player[plr].playerType = 3;
}

// [D] [T]
void DestroyPlayer(int plr, int fully)
{
	if (PlayerStartInfo[plr]->flags & 2)
	{
		SetNullPlayerDontKill(plr);
		return;
	}

	if (fully) 
	{
		if (player[plr].playerType == 1 && player[plr].playerCarId != gThePlayerCar) 
			PingOutCar(&car_data[player[plr].playerCarId]);

		if (player[plr].pPed != NULL) 
			DestroyPedestrian(player[plr].pPed);
	}

	player[plr].playerType = 3;
	player[plr].playerCarId = -1;
	player[plr].pPed = NULL;
}

// [D] [T]
void FindNextCutChange(int cameracnt)
{
	bool found;
	int nextframe;
	int count;

	found = false;
	nextframe = 100001;
	count = 0;

	do {
		if (cameracnt <= CutsceneCamera[count].FrameCnt && 
			CutsceneCamera[count].FrameCnt < nextframe)
		{
			found = true;
			nextframe = CutsceneCamera[count].FrameCnt;
			CutNextChange = &CutsceneCamera[count];
		}

		count++;
	} while (count < MAX_REPLAY_CAMERAS);

	if (!found)
		CutNextChange->next = -2;
}

// [D] [T]
int LoadCutsceneToReplayBuffer(int residentCutscene)
{
	REPLAY_SAVE_HEADER *rheader;
	REPLAY_STREAM_HEADER *sheader;
	char *pt;

	rheader = (REPLAY_SAVE_HEADER *)CutsceneBuffer.residentPointers[residentCutscene];

	if (rheader == NULL || 
		rheader && (rheader->magic != DRIVER2_REPLAY_MAGIC && 
					rheader->magic != REDRIVER2_CHASE_MAGIC ||	// [A]
					rheader->NumReplayStreams == 0) )
	{
		ShowCutsceneError();
		return 0;
	}

	CutsceneStreamIndex = NumReplayStreams; //rheader->NumReplayStreams;
	NumCutsceneStreams = rheader->NumReplayStreams;
	
	CutsceneReplayStart = replayptr;
	CutsceneEventTrigger = rheader->CutsceneEvent;

	pt = (char *)(rheader + 1);
	
	// add to existing replay streams
	for (int i = NumReplayStreams; i < (NumReplayStreams + rheader->NumReplayStreams); i++)
	{
		sheader = (REPLAY_STREAM_HEADER *)pt;
		pt += sizeof(REPLAY_STREAM_HEADER);

		REPLAY_STREAM* destStream = &ReplayStreams[i];

		// copy source type
		memcpy((u_char*)&destStream->SourceType, (u_char*)&sheader->SourceType, sizeof(STREAM_SOURCE));

		// init buffers
		destStream->InitialPadRecordBuffer = (PADRECORD*)replayptr;
		destStream->PadRecordBuffer = (PADRECORD*)replayptr;
		destStream->PadRecordBufferEnd = (PADRECORD *)(replayptr + sheader->Size);
		destStream->padCount = sheader->Size;
		destStream->length = sheader->Length;
		destStream->playbackrun = 0;

		int size = (sheader->Size + sizeof(PADRECORD)) & -4;

		// copy pad data and advance buffer
		memcpy((u_char*)replayptr, (u_char*)pt, size);
		replayptr += size;

		pt += size;
	}

	NumReplayStreams += NumCutsceneStreams;

	// [A] REDRIVER2 chase replays skip cameras
	if (rheader->magic == REDRIVER2_CHASE_MAGIC)
	{
		CutsceneCamera = NULL;
	}
	else
	{
		// copy cutscene cameras and pings
		CutsceneCamera = (PLAYBACKCAMERA*)replayptr;

		memcpy((u_char*)CutsceneCamera, (u_char*)pt, sizeof(PLAYBACKCAMERA) * MAX_REPLAY_CAMERAS);
		replayptr += sizeof(PLAYBACKCAMERA) * MAX_REPLAY_CAMERAS;
		pt += sizeof(PLAYBACKCAMERA) * MAX_REPLAY_CAMERAS;
	}

	memcpy((u_char*)PingBuffer, (u_char*)pt, sizeof(PING_PACKET) * MAX_REPLAY_PINGS);
	PingBufferPos = 0;

	return 1;
}

// [D] [T]
int LoadCutsceneToBuffer(int subindex)
{
	int offset;
	int size;

	CUTSCENE_HEADER header;
	char filename[64];
	
	SelectCutsceneFile(filename, 0, subindex);
	
	printInfo("Loading cutscene '%s' (%d)\n", filename, subindex);

	if (FileExists(filename))
	{
		LoadfileSeg(filename, (char *)&header, 0, sizeof(CUTSCENE_HEADER));

		if (header.data[subindex].offset != 0xffff)
		{
			offset = header.data[subindex].offset * 4;
			size = header.data[subindex].size;

			// [A] if starting game - allocate new buffer
			if (CutsceneBuffer.currentPointer == NULL)
			{
				if(CutsceneInReplayBuffer)
				{
					CutsceneBuffer.currentPointer = CutsceneBuffer.buffer;
					CutsceneBuffer.bytesFree = CutsceneBuffer.reservedSize;
				}
				else
				{
					// obtain temp buffer
					CutsceneBuffer.currentPointer = D_TEMPALLOC(size);
					CutsceneBuffer.bytesFree = size;
					CutsceneBuffer.reservedSize = size;
				}
			}

			/*

			WE DON'T NEED IT ANYMORE! EVEN ON PSX!

			// we keep fallback here
			if (size > CutsceneBuffer.bytesFree)
			{
				printWarning("WARNING - Using leadAI/pathAI buffer for cutscene!\n");

				// load into lead/path AI buffer
				leadAILoaded = 0;
				pathAILoaded = 0;

				CutsceneBuffer.currentPointer = (char*)_other_buffer2;
				CutsceneBuffer.bytesFree = 0xC000;	
			}*/

			LoadfileSeg(filename, CutsceneBuffer.currentPointer, offset, size);

			CutsceneBuffer.residentCutscenes[CutsceneBuffer.numResident] = subindex;
			CutsceneBuffer.residentPointers[CutsceneBuffer.numResident] = CutsceneBuffer.currentPointer;
			CutsceneBuffer.numResident++;
			
			CutsceneBuffer.currentPointer += size;
			CutsceneBuffer.bytesFree -= size;

			return 1;
		}
	}

	ShowCutsceneError();

	return 0;
}

// [A] From Code Review
void ShowCutsceneError(void)
{
	RECT16 rect;

	printError("Cutscene initialisation error!\n");
	SetDispMask(0);
	DrawSync(0);

	rect.x = 0;
	rect.y = 0;
	rect.w = 320;
	rect.h = 256;

	ClearImage2(&rect, 0,0,0);
	DrawSync(0);

	SetTextColour(128, 0, 0);
	PrintStringCentred("CUTSCENE ERROR!", 0x78);

	DrawSync(0);
	SetDispMask(1);

	VSync(20);
}

// [D] [T]
int LoadCutsceneInformation(int cutscene)
{
	int i;

	if (cutscene == PreLoadedCutscene) 
	{
		PreLoadedCutscene = -1;
		return 1;
	}

	ReleaseInGameCutscene();

	for(i = 0; i < 4; i++)
	{
		if (cutscene == CutsceneBuffer.residentCutscenes[i])
		{
			if (LoadCutsceneToReplayBuffer(i))
			{
				CutsceneBuffer.residentCutscenes[i] = 0xFF;
				CutsceneBuffer.residentPointers[i] = NULL;
				return 1;
			}
		}
	}

	return 0;
}

// [D] [T]
void FreeCutsceneBuffer(void)
{
	int i;

	for (i = 0; i < 4; i++)
	{
		CutsceneBuffer.residentCutscenes[i] = -1;
		CutsceneBuffer.residentPointers[i] = NULL;
	}

	CutsceneBuffer.numResident = 0;
	CutsceneBuffer.currentPointer = CutsceneBuffer.buffer;
	CutsceneBuffer.bytesFree = CutsceneBuffer.reservedSize;
}

// [D] [T]
int IsCutsceneResident(int cutscene)
{
	int i;

	if (cutscene == PreLoadedCutscene)
		return 1;

	for (i = 0; i < 4; i++)
	{
		if (CutsceneBuffer.residentCutscenes[i] == cutscene)
			return 1;
	}

	return 0;
}





