summaryrefslogblamecommitdiffstats
path: root/src/control/Record.cpp
blob: e858bc1da52befa89cf384b71b797e0746d50b49 (plain) (tree)
1
2
3
4
5
6
7
8
9
                   
                    

                   


                    
                      
                  

                             
 
                                                                



                                                                               
 
































































































































































                                                                                                                                                                           




















































                                                                                                                                            

                                                                                                                   

































































































                                                                                                                                             





                                                           
                                          


                                                                 






                                                                                           
 

                                                                                                       

                                                                              
 
 



                                                                                                     
                                                                                                 

































































































































































                                                                                                                           
 
#include "common.h"
#include "patcher.h"
#include "Record.h"

#include "FileMgr.h"
#include "Pad.h"
#include "Pools.h"
#include "Streaming.h"
#include "Timer.h"
#include "VehicleModelInfo.h"
#include "World.h"

uint16 &CRecordDataForGame::RecordingState = *(uint16*)0x95CC24;
uint8*& CRecordDataForGame::pDataBuffer = *(uint8**)0x8F1B70;
uint8*& CRecordDataForGame::pDataBufferPointer = *(uint8**)0x8F1AB0;
int& CRecordDataForGame::FId = *(int*)0x885BA4;
tGameBuffer& CRecordDataForGame::pDataBufferForFrame = *(tGameBuffer*)0x72CED0;

#define MEMORY_FOR_GAME_RECORD (150000)

void CRecordDataForGame::Init(void)
{
	RecordingState = STATE_NONE;
	if (pDataBuffer)
		delete[] pDataBuffer;
	pDataBufferPointer = nil;
	pDataBuffer = nil;
#ifndef GTA_PS2 // this stuff is not present on PS2
	FId = CFileMgr::OpenFile("playback.dat", "r");
	if (FId <= 0) {
		if ((FId = CFileMgr::OpenFile("record.dat", "r")) <= 0)
			RecordingState = STATE_NONE;
		else {
			CFileMgr::CloseFile(FId);
			FId = CFileMgr::OpenFileForWriting("record.dat");
			RecordingState = STATE_RECORD;
		}
	}
	else {
		RecordingState = STATE_PLAYBACK;
	}
	if (RecordingState == STATE_PLAYBACK) {
		pDataBufferPointer = (uint8*)malloc(MEMORY_FOR_GAME_RECORD);
		pDataBuffer = pDataBufferPointer;
		pDataBuffer[CFileMgr::Read(FId, (char*)pDataBufferPointer, MEMORY_FOR_GAME_RECORD) + 8] = -1;
		CFileMgr::CloseFile(FId);
	}
#else
	RecordingState = STATE_NONE; // second time to make sure
#endif
}

void CRecordDataForGame::SaveOrRetrieveDataForThisFrame(void)
{
	switch (RecordingState) {
	case STATE_RECORD:
	{
		pDataBufferForFrame.m_fTimeStep = CTimer::GetTimeStep();
		pDataBufferForFrame.m_nTimeInMilliseconds = CTimer::GetTimeInMilliseconds();
		pDataBufferForFrame.m_nSizeOfPads[0] = 0;
		pDataBufferForFrame.m_nSizeOfPads[1] = 0;
		pDataBufferForFrame.m_nChecksum = CalcGameChecksum();
		uint8* pController1 = PackCurrentPadValues(pDataBufferForFrame.m_ControllerBuffer, &CPad::GetPad(0)->OldState, &CPad::GetPad(0)->NewState);
		pDataBufferForFrame.m_nSizeOfPads[0] = (pController1 - pDataBufferForFrame.m_ControllerBuffer) / 2;
		uint8* pController2 = PackCurrentPadValues(pController1, &CPad::GetPad(1)->OldState, &CPad::GetPad(1)->NewState);
		pDataBufferForFrame.m_nSizeOfPads[1] = (pController2 - pController1) / 2;
		uint8* pEndPtr = pController2;
		if ((pDataBufferForFrame.m_nSizeOfPads[0] + pDataBufferForFrame.m_nSizeOfPads[1]) & 1)
			pEndPtr += 2;
		CFileMgr::Write(FId, (char*)&pDataBufferForFrame, pEndPtr - (uint8*)&pDataBufferForFrame);
		break;
	}
	case STATE_PLAYBACK:
		if (pDataBufferPointer[8] == -1)
			CPad::GetPad(0)->NewState.Clear();
		else {
			tGameBuffer* pData = (tGameBuffer*)pDataBufferPointer;
			CTimer::SetTimeInMilliseconds(pData->m_nTimeInMilliseconds);
			CTimer::SetTimeStep(pData->m_fTimeStep);
			uint8 size1 = pData->m_nSizeOfPads[0];
			uint8 size2 = pData->m_nSizeOfPads[1];
			pDataBufferPointer = (uint8*)&pData->m_ControllerBuffer;
			pDataBufferPointer = UnPackCurrentPadValues(pDataBufferPointer, size1, &CPad::GetPad(0)->NewState);
			pDataBufferPointer = UnPackCurrentPadValues(pDataBufferPointer, size2, &CPad::GetPad(1)->NewState);
			if ((size1 + size2) & 1)
				pDataBufferPointer += 2;
			if (pData->m_nChecksum != CalcGameChecksum())
				printf("Playback out of sync\n");
		}
	}
}

#define PROCESS_BUTTON_STATE_STORE(buf, os, ns, field, id) \
	do { \
		if (os->field != os->field){ \
			*buf++ = id; \
			*buf++ = ns->field; \
		} \
	} while (0);

uint8* CRecordDataForGame::PackCurrentPadValues(uint8* buf, CControllerState* os, CControllerState* ns)
{
	PROCESS_BUTTON_STATE_STORE(buf, os, ns, LeftStickX, 0);
	PROCESS_BUTTON_STATE_STORE(buf, os, ns, LeftStickY, 1);
	PROCESS_BUTTON_STATE_STORE(buf, os, ns, LeftShoulder1, 2);
	PROCESS_BUTTON_STATE_STORE(buf, os, ns, RightStickY, 3);
	PROCESS_BUTTON_STATE_STORE(buf, os, ns, LeftShoulder1, 4);
	PROCESS_BUTTON_STATE_STORE(buf, os, ns, LeftShoulder2, 5);
	PROCESS_BUTTON_STATE_STORE(buf, os, ns, RightShoulder1, 6);
	PROCESS_BUTTON_STATE_STORE(buf, os, ns, RightShoulder2, 7);
	PROCESS_BUTTON_STATE_STORE(buf, os, ns, DPadUp, 8);
	PROCESS_BUTTON_STATE_STORE(buf, os, ns, DPadDown, 9);
	PROCESS_BUTTON_STATE_STORE(buf, os, ns, DPadLeft, 10);
	PROCESS_BUTTON_STATE_STORE(buf, os, ns, DPadRight, 11);
	PROCESS_BUTTON_STATE_STORE(buf, os, ns, Start, 12);
	PROCESS_BUTTON_STATE_STORE(buf, os, ns, Select, 13);
	PROCESS_BUTTON_STATE_STORE(buf, os, ns, Square, 14);
	PROCESS_BUTTON_STATE_STORE(buf, os, ns, Triangle, 15);
	PROCESS_BUTTON_STATE_STORE(buf, os, ns, Cross, 16);
	PROCESS_BUTTON_STATE_STORE(buf, os, ns, Circle, 17);
	PROCESS_BUTTON_STATE_STORE(buf, os, ns, LeftShock, 18);
	PROCESS_BUTTON_STATE_STORE(buf, os, ns, RightShock, 19);
	return buf;
}
#undef PROCESS_BUTTON_STATE_STORE

#define PROCESS_BUTTON_STATE_RESTORE(buf, state, field, id) case id: state->field = *buf++; break;

uint8* CRecordDataForGame::UnPackCurrentPadValues(uint8* buf, uint8 total, CControllerState* state)
{
	for (uint8 i = 0; i < total; i++) {
		switch (*buf++) {
			PROCESS_BUTTON_STATE_RESTORE(buf, state, LeftStickX, 0);
			PROCESS_BUTTON_STATE_RESTORE(buf, state, LeftStickY, 1);
			PROCESS_BUTTON_STATE_RESTORE(buf, state, LeftShoulder1, 2);
			PROCESS_BUTTON_STATE_RESTORE(buf, state, RightStickY, 3);
			PROCESS_BUTTON_STATE_RESTORE(buf, state, LeftShoulder1, 4);
			PROCESS_BUTTON_STATE_RESTORE(buf, state, LeftShoulder2, 5);
			PROCESS_BUTTON_STATE_RESTORE(buf, state, RightShoulder1, 6);
			PROCESS_BUTTON_STATE_RESTORE(buf, state, RightShoulder2, 7);
			PROCESS_BUTTON_STATE_RESTORE(buf, state, DPadUp, 8);
			PROCESS_BUTTON_STATE_RESTORE(buf, state, DPadDown, 9);
			PROCESS_BUTTON_STATE_RESTORE(buf, state, DPadLeft, 10);
			PROCESS_BUTTON_STATE_RESTORE(buf, state, DPadRight, 11);
			PROCESS_BUTTON_STATE_RESTORE(buf, state, Start, 12);
			PROCESS_BUTTON_STATE_RESTORE(buf, state, Select, 13);
			PROCESS_BUTTON_STATE_RESTORE(buf, state, Square, 14);
			PROCESS_BUTTON_STATE_RESTORE(buf, state, Triangle, 15);
			PROCESS_BUTTON_STATE_RESTORE(buf, state, Cross, 16);
			PROCESS_BUTTON_STATE_RESTORE(buf, state, Circle, 17);
			PROCESS_BUTTON_STATE_RESTORE(buf, state, LeftShock, 18);
			PROCESS_BUTTON_STATE_RESTORE(buf, state, RightShock, 19);
		}
	}
	return buf;
}

#undef PROCESS_BUTTON_STATE_RESTORE

uint16 CRecordDataForGame::CalcGameChecksum(void)
{
	uint32 checksum = 0;
	int i = CPools::GetPedPool()->GetSize();
	while (i--) {
		CPed* pPed = CPools::GetPedPool()->GetSlot(i);
		if (!pPed)
			continue;
		checksum ^= pPed->GetModelIndex() ^ *(uint32*)&pPed->GetPosition().z ^ *(uint32*)&pPed->GetPosition().y ^ *(uint32*)&pPed->GetPosition().x;
	}
	i = CPools::GetVehiclePool()->GetSize();
	while (i--) {
		CVehicle* pVehicle = CPools::GetVehiclePool()->GetSlot(i);
		if (!pVehicle)
			continue;
		checksum ^= pVehicle->GetModelIndex() ^ *(uint32*)&pVehicle->GetPosition().z ^ *(uint32*)&pVehicle->GetPosition().y ^ *(uint32*)&pVehicle->GetPosition().x;
	}
	return checksum ^ checksum >> 16;
}

uint8& CRecordDataForChase::Status = *(uint8*)0x95CDCE;
int& CRecordDataForChase::PositionChanges = *(int*)0x8F59C8;
uint8& CRecordDataForChase::CurrentCar = *(uint8*)0x95CDC9;
CAutomobile* (&CRecordDataForChase::pChaseCars)[NUM_CHASE_CARS] = *(CAutomobile * (*)[NUM_CHASE_CARS])*(uintptr*)0x6F46A8;
uint32& CRecordDataForChase::AnimStartTime = *(uint32*)0x8F1AEC;
float& CRecordDataForChase::AnimTime = *(float*)0x880F88;
CCarStateEachFrame* (&CRecordDataForChase::pBaseMemForCar)[NUM_CHASE_CARS] = *(CCarStateEachFrame * (*)[NUM_CHASE_CARS])*(uintptr*)0x70EA18;
float& CRecordDataForChase::TimeMultiplier = *(float*)0x8E2A94;
int& CRecordDataForChase::FId2 = *(int*)0x8E2C18;

#define CHASE_SCENE_LENGTH_IN_SECONDS (80)
#define CHASE_SCENE_FRAMES_PER_SECOND (15) // skipping every second frame
#define CHASE_SCENE_FRAMES_IN_RECORDING (CHASE_SCENE_LENGTH_IN_SECONDS * CHASE_SCENE_FRAMES_PER_SECOND)
#define CHASE_SCENE_LENGTH_IN_FRAMES (CHASE_SCENE_FRAMES_IN_RECORDING * 2)

void CRecordDataForChase::Init(void)
{
	Status = STATE_NONE;
	PositionChanges = 0;
	CurrentCar = 0;
	for (int i = 0; i < NUM_CHASE_CARS; i++)
		pChaseCars[i] = nil;
	AnimStartTime = 0;
}

void CRecordDataForChase::SaveOrRetrieveDataForThisFrame(void)
{
	switch (Status) {
	case STATE_NONE:
		return;
	case STATE_RECORD:
	{
		if ((CTimer::GetFrameCounter() & 1) == 0)
			StoreInfoForCar(pChaseCars[CurrentCar], &pBaseMemForCar[CurrentCar][CTimer::GetFrameCounter() / 2]);
		if (CTimer::GetFrameCounter() < CHASE_SCENE_LENGTH_IN_FRAMES * 2)
			return;
		CFileMgr::SetDir("data\\paths");
		sprintf(gString, "chase%d.dat", CurrentCar);
		int fid = CFileMgr::OpenFileForWriting(gString);
		uint32 fs = CHASE_SCENE_LENGTH_IN_FRAMES * sizeof(CCarStateEachFrame);
		printf("FileSize:%d\n", fs);
		CFileMgr::Write(fid, (char*)pBaseMemForCar[CurrentCar], fs);
		CFileMgr::CloseFile(fid);
		CFileMgr::SetDir("");
		sprintf(gString, "car%d.max", CurrentCar);
		int fid2 = CFileMgr::OpenFileForWriting(gString);
		for (int i = 0; i < CHASE_SCENE_FRAMES_IN_RECORDING; i++) {
			// WTF? Was it ever used?
#ifdef FIX_BUGS
			CCarStateEachFrame* pState = pBaseMemForCar[CurrentCar];
#else
			CCarStateEachFrame* pState = (CCarStateEachFrame*)pChaseCars[CurrentCar];
#endif
			CVector right = CVector(pState->rightX, pState->rightY, pState->rightZ) / INT8_MAX;
			CVector forward = CVector(pState->forwardX, pState->forwardY, pState->forwardZ) / INT8_MAX;
			CVector up = CrossProduct(right, forward);
			sprintf(gString, "%f %f %f\n", pState->pos.x, pState->pos.y, pState->pos.z);
			CFileMgr::Write(fid2, gString, strlen(gString) - 1);
			sprintf(gString, "%f %f %f\n", right.x, right.y, right.z);
			CFileMgr::Write(fid2, gString, strlen(gString) - 1);
			sprintf(gString, "%f %f %f\n", forward.x, forward.y, forward.z);
			CFileMgr::Write(fid2, gString, strlen(gString) - 1);
			sprintf(gString, "%f %f %f\n", up.x, up.y, up.z);
			CFileMgr::Write(fid2, gString, strlen(gString) - 1);
		}
		CFileMgr::CloseFile(fid2);
	}
	case STATE_PLAYBACK:
	case STATE_PLAYBACK_BEFORE_RECORDING:
	case STATE_PLAYBACK_INIT:
		break;
	}
}

struct tCoors {
	CVector pos;
	float angle;
};

// I guess developer was filling this with actual data before running the game
tCoors NewCoorsForRecordedCars[7];

void CRecordDataForChase::SaveOrRetrieveCarPositions(void)
{
	switch (Status) {
	case STATE_NONE:
		return;
	case STATE_RECORD:
	case STATE_PLAYBACK_BEFORE_RECORDING:
		for (int i = 0; i < NUM_CHASE_CARS; i++) {
			if (i != CurrentCar && CTimer::GetFrameCounter()) {
				RestoreInfoForCar(pChaseCars[i], &pBaseMemForCar[i][CTimer::GetFrameCounter() / 2], false);
				pChaseCars[i]->GetMatrix().UpdateRW();
				pChaseCars[i]->UpdateRwFrame();
			}
		}
		if (Status == STATE_PLAYBACK_BEFORE_RECORDING && CTimer::GetFrameCounter()) {
			RestoreInfoForCar(pChaseCars[CurrentCar], &pBaseMemForCar[CurrentCar][CTimer::GetFrameCounter() / 2], false);
			pChaseCars[CurrentCar]->GetMatrix().UpdateRW();
			pChaseCars[CurrentCar]->UpdateRwFrame();
		}
		if (CPad::GetPad(0)->GetLeftShockJustDown() && CPad::GetPad(0)->GetRightShockJustDown()) {
			if (!CPad::GetPad(0)->GetRightShockJustDown()) {
				pChaseCars[CurrentCar]->GetPosition() = NewCoorsForRecordedCars[PositionChanges].pos;
				pChaseCars[CurrentCar]->SetMoveSpeed(0.0f, 0.0f, 0.0f);
				pChaseCars[CurrentCar]->GetMatrix().SetRotateZOnly(DEGTORAD(NewCoorsForRecordedCars[PositionChanges].angle));
				++PositionChanges;
			}
			if (Status == STATE_PLAYBACK_BEFORE_RECORDING) {
				Status = STATE_RECORD;
				pChaseCars[CurrentCar]->m_status = STATUS_PLAYER;
			}
		}
		break;
	case STATE_PLAYBACK_INIT:
		Status = STATE_PLAYBACK;
		break;
	case STATE_PLAYBACK:
	{
		TimeMultiplier += CTimer::GetTimeStepNonClippedInSeconds();
		float EndOfFrameTime = CHASE_SCENE_FRAMES_PER_SECOND * min(CHASE_SCENE_LENGTH_IN_SECONDS, TimeMultiplier);
		for (int i = 0; i < NUM_CHASE_CARS; i++) {
			if (!pBaseMemForCar[i])
				continue;
			if (!pChaseCars[i])
				continue;
			if (EndOfFrameTime < CHASE_SCENE_FRAMES_IN_RECORDING - 1) {
				int FlooredEOFTime = EndOfFrameTime;
				RestoreInfoForCar(pChaseCars[i], &pBaseMemForCar[i][FlooredEOFTime], false);
				CMatrix tmp;
				float dp = EndOfFrameTime - FlooredEOFTime;
				RestoreInfoForMatrix(tmp, &pBaseMemForCar[i][FlooredEOFTime + 1]);
				pChaseCars[i]->GetRight() += (tmp.GetRight() - pChaseCars[i]->GetRight()) * dp;
				pChaseCars[i]->GetForward() += (tmp.GetForward() - pChaseCars[i]->GetForward()) * dp;
				pChaseCars[i]->GetUp() += (tmp.GetUp() - pChaseCars[i]->GetUp()) * dp;
				pChaseCars[i]->GetPosition() += (tmp.GetPosition() - pChaseCars[i]->GetPosition()) * dp;
			}
			else{
				RestoreInfoForCar(pChaseCars[i], &pBaseMemForCar[i][CHASE_SCENE_FRAMES_IN_RECORDING - 1], true);
				if (i == 0)
					pChaseCars[i]->GetPosition().z += 0.2f;
			}
			pChaseCars[i]->GetMatrix().UpdateRW();
			pChaseCars[i]->UpdateRwFrame();
			pChaseCars[i]->RemoveAndAdd();
		}
		break;
	}
	}
}

void CRecordDataForChase::StoreInfoForCar(CAutomobile* pCar, CCarStateEachFrame* pState)
{
	pState->rightX = INT8_MAX * pCar->GetRight().x;
	pState->rightY = INT8_MAX * pCar->GetRight().y;
	pState->rightZ = INT8_MAX * pCar->GetRight().z;
	pState->forwardX = INT8_MAX * pCar->GetForward().x;
	pState->forwardY = INT8_MAX * pCar->GetForward().y;
	pState->forwardZ = INT8_MAX * pCar->GetForward().z;
	pState->pos = pCar->GetPosition();
	pState->velX = 0.5f * INT16_MAX * pCar->GetMoveSpeed().x;
	pState->velY = 0.5f * INT16_MAX * pCar->GetMoveSpeed().y;
	pState->velZ = 0.5f * INT16_MAX * pCar->GetMoveSpeed().z;
	pState->wheel = 20 * pCar->m_fSteerAngle;
	pState->gas = 100 * pCar->m_fGasPedal;
	pState->brake = 100 * pCar->m_fBrakePedal;
	pState->handbrake = pCar->bIsHandbrakeOn;
}

void CRecordDataForChase::RestoreInfoForMatrix(CMatrix& matrix, CCarStateEachFrame* pState)
{
	matrix.GetRight() = CVector(pState->rightX, pState->rightY, pState->rightZ) / INT8_MAX;
	matrix.GetForward() = CVector(pState->forwardX, pState->forwardY, pState->forwardZ) / INT8_MAX;
	matrix.GetUp() = CrossProduct(matrix.GetRight(), matrix.GetForward());
	matrix.GetPosition() = pState->pos;
}

void CRecordDataForChase::RestoreInfoForCar(CAutomobile* pCar, CCarStateEachFrame* pState, bool stop)
{
	CVector oldPos = pCar->GetPosition();
	RestoreInfoForMatrix(pCar->GetMatrix(), pState);
	pCar->SetMoveSpeed(CVector(pState->velX, pState->velY, pState->velZ) / INT16_MAX / 0.5f);
	pCar->SetTurnSpeed(0.0f, 0.0f, 0.0f);
	pCar->m_fSteerAngle = pState->wheel / 20.0f;
	pCar->m_fGasPedal = pState->gas / 100.0f;
	pCar->m_fBrakePedal = pState->brake / 100.0f;
	pCar->bIsHandbrakeOn = pState->handbrake;
	if ((oldPos - pCar->GetPosition()).Magnitude() > 15.0f) {
		if (pCar == pChaseCars[14]) {
			pCar->m_currentColour1 = 58;
			pCar->m_currentColour2 = 1;
		}
		else
			pCar->GetModelInfo()->ChooseVehicleColour(pCar->m_currentColour1, pCar->m_currentColour2);
	}
	pCar->m_fHealth = min(pCar->m_fHealth, 500.0f);
	if (stop) {
		pCar->m_fGasPedal = 0.0f;
		pCar->m_fBrakePedal = 0.0f;
		pCar->SetMoveSpeed(0.0f, 0.0f, 0.0f);
		pCar->bIsHandbrakeOn = false;
	}
}

void CRecordDataForChase::ProcessControlCars(void)
{
	if (Status != STATE_PLAYBACK)
		return;
	for (int i = 0; i < NUM_CHASE_CARS; i++) {
		if (pChaseCars[i])
			pChaseCars[i]->ProcessControl();
	}
}

#if (defined(GTA_PS2) || defined(FIX_BUGS))
bool CRecordDataForChase::ShouldThisPadBeLeftAlone(uint8 pad)
{
	// may be wrong
	if (Status == STATE_NONE || Status == STATE_PLAYBACK)
		return false;
	return pad != 0;
}
#endif

void CRecordDataForChase::GiveUsACar(int32 mi, CVector pos, float angle, CAutomobile** ppCar, uint8 colour1, uint8 colour2)
{
	CStreaming::RequestModel(mi, STREAMFLAGS_DEPENDENCY);
	CStreaming::LoadAllRequestedModels(false);
	if (!CStreaming::HasModelLoaded(mi))
		return;
	CAutomobile* pCar = new CAutomobile(mi, MISSION_VEHICLE);
	pCar->GetPosition() = pos;
	pCar->m_status = STATUS_PLAYER_PLAYBACKFROMBUFFER;
	pCar->GetMatrix().SetRotateZOnly(DEGTORAD(angle));
	pCar->pDriver = nil;
	pCar->m_currentColour1 = colour1;
	pCar->m_currentColour2 = colour2;
	CWorld::Add(pCar);
	*ppCar = pCar;
}

void RemoveUnusedCollision(void)
{
	static const char* dontDeleteArray[] = {
		"rd_SrRoad2A50", "rd_SrRoad2A20", "rd_CrossRda1w22", "rd_CrossRda1rw22",
		"road_broadway02", "road_broadway01", "com_21way5", "com_21way50",
		"cm1waycrosscom", "com_21way20", "com_21way10", "road_broadway04",
		"com_rvroads52", "com_roadsrv", "com_roadkb23", "com_roadkb22"
	};
	for (int i = 0; i < ARRAY_SIZE(dontDeleteArray); i++)
		CModelInfo::GetModelInfo(dontDeleteArray[i], nil)->GetColModel()->level = LEVEL_NONE;
	CModelInfo::RemoveColModelsFromOtherLevels(LEVEL_NONE);
	for (int i = 0; i < ARRAY_SIZE(dontDeleteArray); i++)
		CModelInfo::GetModelInfo(dontDeleteArray[i], nil)->GetColModel()->level = LEVEL_COMMERCIAL;
}

void CRecordDataForChase::StartChaseScene(float startTime)
{
	char filename[28];
	SetUpCarsForChaseScene();
	Status = STATE_PLAYBACK;
	AnimTime = startTime;
	AnimStartTime = CTimer::GetTimeInMilliseconds();
	RemoveUnusedCollision();
	CStreaming::RemoveIslandsNotUsed(LEVEL_SUBURBAN);
	CGame::TidyUpMemory(true, true);
	CStreaming::ImGonnaUseStreamingMemory();
	CFileMgr::SetDir("data\\paths");
	for (int i = 0; i < NUM_CHASE_CARS; i++) {
		if (!pChaseCars[i]) {
			pBaseMemForCar[i] = nil;
			continue;
		}
		sprintf(filename, "chase%d.dat", i);
		FId2 = CFileMgr::OpenFile(filename, "rb");
		if (FId2 <= 0) {
			pBaseMemForCar[i] = nil;
			continue;
		}
		pBaseMemForCar[i] = new CCarStateEachFrame[CHASE_SCENE_FRAMES_IN_RECORDING];
		for (int j = 0; j < CHASE_SCENE_FRAMES_IN_RECORDING; j++) {
			CFileMgr::Read(FId2, (char*)&pBaseMemForCar[i][j], sizeof(CCarStateEachFrame));
			CFileMgr::Seek(FId2, sizeof(CCarStateEachFrame), 1);
		}
		CFileMgr::CloseFile(FId2);
	}
	CFileMgr::SetDir("");
	CStreaming::IHaveUsedStreamingMemory();
	TimeMultiplier = 0.0f;
}

void CRecordDataForChase::CleanUpChaseScene(void)
{
	if (Status != STATE_PLAYBACK_INIT && Status != STATE_PLAYBACK)
		return;
	Status = STATE_NONE;
	CleanUpCarsForChaseScene();
	for (int i = 0; i < NUM_CHASE_CARS; i++) {
		if (pBaseMemForCar[i]) {
			delete[] pBaseMemForCar[i];
			pBaseMemForCar[i] = nil;
		}
	}
}

void CRecordDataForChase::SetUpCarsForChaseScene(void)
{
	GiveUsACar(MI_POLICE, CVector(273.54221f, -1167.1907f, 24.880601f), 63.0f, &pChaseCars[0], 2, 1);
	GiveUsACar(MI_ENFORCER, CVector(231.1783f, -1388.8322f, 25.978201f), 90.0f, &pChaseCars[1], 2, 1);
	GiveUsACar(MI_TAXI, CVector(184.3156f, -1473.251f, 25.978201f), 0.0f, &pChaseCars[4], 6, 6);
	GiveUsACar(MI_CHEETAH, CVector(173.8868f, -1377.6514f, 25.978201f), 0.0f, &pChaseCars[6], 4, 5);
	GiveUsACar(MI_STINGER, CVector(102.5946f, -943.93628f, 25.9781f), 270.0f, &pChaseCars[7], 53, 53);
	GiveUsACar(MI_CHEETAH, CVector(-177.7157f, -862.18652f, 25.978201f), 155.0f, &pChaseCars[10], 41, 1);
	GiveUsACar(MI_STINGER, CVector(-170.56979f, -889.02362f, 25.978201f), 154.0f, &pChaseCars[11], 10, 10);
	GiveUsACar(MI_KURUMA, CVector(402.60809f, -917.49628f, 37.381001f), 90.0f, &pChaseCars[14], 34, 1);
	GiveUsACar(MI_TAXI, CVector(-33.496201f, -938.4563f, 25.9781f), 266.0f, &pChaseCars[16], 6, 6);
	GiveUsACar(MI_KURUMA, CVector(49.363098f, -987.60498f, 25.9781f), 0.0f, &pChaseCars[18], 51, 1);
	GiveUsACar(MI_TAXI, CVector(179.0049f, -1154.6686f, 25.9781f), 0.0f, &pChaseCars[19], 6, 76);
	GiveUsACar(MI_RUMPO, CVector(-28.9762f, -1031.3367f, 25.990601f), 242.0f, &pChaseCars[2], 1, 75);
	GiveUsACar(MI_PATRIOT, CVector(114.1564f, -796.69379f, 24.978201f), 180.0f, &pChaseCars[3], 0, 0);
}

void CRecordDataForChase::CleanUpCarsForChaseScene(void)
{
	for (int i = 0; i < NUM_CHASE_CARS; i++)
		RemoveCarFromChase(i);
}

void CRecordDataForChase::RemoveCarFromChase(int32 i)
{
	if (!pChaseCars[i])
		return;
	CWorld::Remove(pChaseCars[i]);
	delete pChaseCars[i];
	pChaseCars[i] = nil;
}

CVehicle* CRecordDataForChase::TurnChaseCarIntoScriptCar(int32 i)
{
	CVehicle* pVehicle = pChaseCars[i];
	pChaseCars[i] = nil;
	pVehicle->m_status = STATUS_PHYSICS;
	return pVehicle;
}