3759 lines
128 KiB
C++
3759 lines
128 KiB
C++
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// FILE : record.cpp
|
|
// PURPOSE : Record the input and elapse of time so that we can accurately
|
|
// play back the game.
|
|
// This stuff was originally used for the start of GTAIII (the jail break). It was changed
|
|
// for SA to allow for single cars to be recorded and played back easily by the level designers.
|
|
// AUTHOR : Obbe.
|
|
// CREATED : 28/2/00
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Rage headers
|
|
#include "phbullet/SimdTransformUtil.h"
|
|
#include "streaming/streamingmodule.h"
|
|
#include "system/timer.h"
|
|
|
|
// Framework Headers
|
|
#include "fwanimation/animmanager.h"
|
|
#include "grcore/debugdraw.h"
|
|
#include "fwmaths/vector.h"
|
|
#include "fwsys/fileExts.h"
|
|
|
|
// Game headers
|
|
#include "control/gamelogic.h"
|
|
#include "control/record.h"
|
|
#include "control/replay/replay.h"
|
|
#include "core/game.h"
|
|
#include "debug/DebugScene.h"
|
|
#include "vehicleAi/vehicleintelligence.h"
|
|
#include "vehicleAi/Task/TaskVehicleFollowRecording.h"
|
|
#include "core/game.h"
|
|
#include "cutscene/CutSceneManagerNew.h"
|
|
#include "debug/debug.h"
|
|
#include "game/modelIndices.h"
|
|
#include "game/clock.h"
|
|
#include "modelInfo/vehiclemodelinfo.h"
|
|
#include "peds/ped.h"
|
|
#include "peds/pedpopulation.h"
|
|
#include "peds/pedintelligence.h"
|
|
#include "physics/physics.h"
|
|
#include "phcore/phmath.h"
|
|
#include "scene/world/gameWorld.h"
|
|
#include "system/fileMgr.h"
|
|
#include "system/pad.h"
|
|
#include "vehicles/bike.h"
|
|
#include "vehicles/train.h"
|
|
#include "vehicles/trailer.h"
|
|
#include "Task/Vehicle/TaskCar.h"
|
|
#include "Task/Vehicle/TaskVehicleWeapon.h"
|
|
|
|
AI_OPTIMISATIONS()
|
|
AI_VEHICLE_OPTIMISATIONS()
|
|
VEHICLE_OPTIMISATIONS()
|
|
|
|
#define DEFAULT_RECORDING_PATH "assets:/recordings/car/"
|
|
|
|
#if __BANK
|
|
PF_PAGE(VehicleRecordingPage, "Vehicle Recording");
|
|
|
|
PF_GROUP(VehicleRecordingMetrics);
|
|
PF_LINK(VehicleRecordingPage, VehicleRecordingMetrics);
|
|
|
|
PF_VALUE_FLOAT(vehicleEffectiveSpeed, VehicleRecordingMetrics);
|
|
PF_VALUE_FLOAT(vehicleActualSpeed, VehicleRecordingMetrics);
|
|
|
|
static Vector3 g_VehiclePositionOnPreviousUpdate = VEC3_MAX;
|
|
bkButton* CVehicleRecordingMgr::ms_pCreateAudioWidgetsButton = NULL;
|
|
#endif // __BANK
|
|
|
|
#if !__FINAL
|
|
static const float g_MinVehicleSpeedToFixUp = 1.0f;
|
|
static const u32 g_MaxVehicleTimeStampFixUpInMilliseconds = 33; //One physics time-slice at 15fps.
|
|
static const u32 g_NumStatesToFixUpBeforePinningTimeStamp = 20;
|
|
|
|
CVehicleRecordingMgr::CRecording CVehicleRecordingMgr::ms_vehiclesRecordings[MAX_CARS_RECORDED_NUMBER];
|
|
#endif
|
|
|
|
Vec3V CVehicleRecordingMgr::PlaybackAdditionalRotation[MAX_CARS_PLAYBACK_NUMBER];
|
|
Vec3V CVehicleRecordingMgr::PlaybackPreviousAdditionalRotation[MAX_CARS_RECORDED_NUMBER];
|
|
Vec3V CVehicleRecordingMgr::PlaybackLocalPositionOffset[MAX_CARS_PLAYBACK_NUMBER];
|
|
Vec3V CVehicleRecordingMgr::PlaybackGlobalPositionOffset[MAX_CARS_PLAYBACK_NUMBER];
|
|
RegdVeh CVehicleRecordingMgr::pVehicleForPlayback[MAX_CARS_PLAYBACK_NUMBER];
|
|
u8 *CVehicleRecordingMgr::pPlaybackBuffer[MAX_CARS_PLAYBACK_NUMBER];
|
|
s32 CVehicleRecordingMgr::PlaybackIndex[MAX_CARS_PLAYBACK_NUMBER];
|
|
s32 CVehicleRecordingMgr::PlaybackBufferSize[MAX_CARS_PLAYBACK_NUMBER];
|
|
float CVehicleRecordingMgr::PlaybackRunningTime[MAX_CARS_PLAYBACK_NUMBER];
|
|
float CVehicleRecordingMgr::PlaybackSpeed[MAX_CARS_PLAYBACK_NUMBER];
|
|
bool CVehicleRecordingMgr::bPlaybackGoingOn[MAX_CARS_PLAYBACK_NUMBER];
|
|
bool CVehicleRecordingMgr::bPlaybackLooped[MAX_CARS_PLAYBACK_NUMBER];
|
|
bool CVehicleRecordingMgr::bPlaybackPaused[MAX_CARS_PLAYBACK_NUMBER];
|
|
bool CVehicleRecordingMgr::bUseCarAI[MAX_CARS_PLAYBACK_NUMBER];
|
|
s32 CVehicleRecordingMgr::aiVechicleRecordingFlags[MAX_CARS_PLAYBACK_NUMBER];
|
|
u32 CVehicleRecordingMgr::aiDelayUntilRevertingBack[MAX_CARS_PLAYBACK_NUMBER];
|
|
u32 CVehicleRecordingMgr::TurnBackIntoFullPlayBackTime[MAX_CARS_PLAYBACK_NUMBER]; // Do we want to turn this back into a full playback and if so; when?
|
|
u32 CVehicleRecordingMgr::DisplayMode[MAX_CARS_PLAYBACK_NUMBER];
|
|
s32 CVehicleRecordingMgr::PlayBackStreamingIndex[MAX_CARS_PLAYBACK_NUMBER];
|
|
|
|
#define SECONDSBEFOREREMOVAL (20)
|
|
#define HIGHESTFILENUMALLOWED (4000)
|
|
// File numbers are split up as follows:
|
|
// 0 - 1999 Game (IV)
|
|
// 2000 - 2999 mp1 (bikers)
|
|
// 3000 - 3999 mp2
|
|
|
|
|
|
|
|
#if __BANK
|
|
#if !__FINAL
|
|
static bool debug_WidgetRecordingPlaying = false;
|
|
static RegdVeh debug_WidgetRecordingPlaybackVehicle;
|
|
static u8 * debug_pDebugBuffer = NULL;
|
|
static int debug_bufferSize;
|
|
static float debug_timeScale = 1.0f;
|
|
static Vector3 debug_AdditionalRotationEulers = VEC3_ZERO;
|
|
static Vec3V debug_LocalPositionOffset = Vec3V(V_ZERO);
|
|
static Vec3V debug_GlobalPositionOffset = Vec3V(V_ZERO);
|
|
int CVehicleRecordingMgr::ms_debug_RecordingNumToPlayWith = 0;
|
|
char CVehicleRecordingMgr::ms_debug_RecordingNameToPlayWith[CAR_RECORDING_NAME_LENGTH] = "";
|
|
rage::bkGroup* CVehicleRecordingMgr::sm_WidgetGroup(NULL);
|
|
#endif
|
|
#endif
|
|
|
|
|
|
// The stuff needed to stream the recordings in.
|
|
CVehicleRecordingStreaming* CVehicleRecordingMgr::sm_StreamingArray;
|
|
s32 CVehicleRecordingMgr::sm_NumPlayBackFiles = 0;
|
|
|
|
|
|
#if __BANK
|
|
#if !__FINAL
|
|
|
|
static bool bShiftRecording = false;
|
|
static bool bFixRecordingTimeStamps = false;
|
|
static float ShiftX = 0.0f;
|
|
static float ShiftY = 0.0f;
|
|
static float ShiftZ = 0.0f;
|
|
static s32 RecordingNumberToBeShifted = -1;
|
|
static char RecordingNameToBeShifted[CAR_RECORDING_NAME_LENGTH] = "";
|
|
|
|
static bool bRenderAllRecordings = false;
|
|
|
|
static float fShiftPlaybackUpValue = 0.0f;
|
|
#endif
|
|
#endif
|
|
|
|
//map to keep track of what filename hashes correspond to what streamingarray indices
|
|
static fwNameRegistrar sm_RecordInfoMap;
|
|
|
|
// PURPOSE: Replacement for MAX_NUM_OF_PLAYBACK_FILES.
|
|
static int s_MaxNumOfPlaybackFiles = 0;
|
|
|
|
bool CVehicleRecordingMgr::sm_bUpdateWithPhysics = true;
|
|
bool CVehicleRecordingMgr::sm_bUpdateBeforePreSimUpdate = false;
|
|
|
|
static inline int GetMaxNumOfPlaybackFiles()
|
|
{
|
|
// Make sure we don't use it until it's been set (by InitMaxNumOfPlaybackFiles()).
|
|
TrapZ(s_MaxNumOfPlaybackFiles);
|
|
|
|
return s_MaxNumOfPlaybackFiles;
|
|
}
|
|
|
|
static int InitMaxNumOfPlaybackFiles()
|
|
{
|
|
const int numPlaybackFiles = fwConfigManager::GetInstance().GetSizeOfPool(ATSTRINGHASH("carrec", 0x0c3824168), CONFIGURED_FROM_FILE);
|
|
Assert(s_MaxNumOfPlaybackFiles == 0 || s_MaxNumOfPlaybackFiles == numPlaybackFiles); // Not expected to change during the game, for now at least.
|
|
s_MaxNumOfPlaybackFiles = numPlaybackFiles;
|
|
|
|
// The size of sm_RecordInfoMap used to be set by its constructor, but now
|
|
// when we don't know the size at compile time, we do it here.
|
|
if(!sm_RecordInfoMap.IsInitialized())
|
|
{
|
|
sm_RecordInfoMap.Init(numPlaybackFiles);
|
|
}
|
|
|
|
return numPlaybackFiles;
|
|
}
|
|
|
|
|
|
PARAM(vehrecordpath, "[record] the absolute path to which car recording .rrr files will be saved");
|
|
|
|
//
|
|
// name: CVehicleRecordingStreamingModule
|
|
// description: Class for registering vehicle recordings with streaming system
|
|
class CVehicleRecordingStreamingModule : public strStreamingModule
|
|
{
|
|
public:
|
|
CVehicleRecordingStreamingModule() : strStreamingModule("carrec",
|
|
VEHICLERECORDING_FILE_ID,
|
|
InitMaxNumOfPlaybackFiles(), // This sets s_MaxNumOfPlaybackFiles, if needed.
|
|
false, false, CVehicleRecording::RORC_VERSION, 512) {}
|
|
|
|
#if !__FINAL
|
|
virtual const char* GetName(strLocalIndex index) const;
|
|
#endif
|
|
virtual strLocalIndex Register(const char* name);
|
|
virtual strLocalIndex FindSlot(const char* name) const {return strLocalIndex(CVehicleRecordingMgr::FindRecordingFile(name));}
|
|
|
|
virtual void Remove(strLocalIndex index) {CVehicleRecordingMgr::Remove(index.Get());}
|
|
virtual void RemoveSlot(strLocalIndex index);
|
|
virtual void PlaceResource(strLocalIndex index, datResourceMap& map, datResourceInfo& header) {CVehicleRecordingMgr::Place(index.Get(), map, header);}
|
|
virtual void* GetPtr(strLocalIndex index) {return CVehicleRecordingMgr::GetPtr(index.Get());}
|
|
|
|
virtual void AddRef(strLocalIndex index, strRefKind /*refKind*/) { CVehicleRecordingMgr::AddRef(index); }
|
|
virtual void RemoveRef(strLocalIndex index, strRefKind /*refKind*/) {CVehicleRecordingMgr::RemoveRef(index); }
|
|
virtual int GetNumRefs(strLocalIndex index) const {return CVehicleRecordingMgr::GetNumRefs(index); }
|
|
virtual int GetDependencies(strLocalIndex UNUSED_PARAM(index), strIndex *UNUSED_PARAM(pIndices), int UNUSED_PARAM(indexArraySize)) const {return 0;}
|
|
|
|
};
|
|
|
|
// --- CVehicleRecordingStreamingModule ---------------------------------------------------------------------
|
|
|
|
strLocalIndex CVehicleRecordingStreamingModule::Register(const char* name)
|
|
{
|
|
strLocalIndex result = strLocalIndex(CVehicleRecordingMgr::RegisterRecordingFile(name));
|
|
|
|
#if USE_PAGED_POOLS_FOR_STREAMING
|
|
AllocateSlot(result);
|
|
#endif // USE_PAGED_POOLS_FOR_STREAMING
|
|
|
|
return result;
|
|
}
|
|
|
|
void CVehicleRecordingStreamingModule::RemoveSlot(strLocalIndex index)
|
|
{
|
|
CVehicleRecordingMgr::RemoveSlot(index.Get());
|
|
|
|
#if USE_PAGED_POOLS_FOR_STREAMING
|
|
FreeObject(index);
|
|
#endif // USE_PAGED_POOLS_FOR_STREAMING
|
|
}
|
|
|
|
#if !__FINAL
|
|
const char* CVehicleRecordingStreamingModule::GetName(strLocalIndex index) const
|
|
{
|
|
static char name[32];
|
|
formatf(name, "%s", CVehicleRecordingMgr::GetRecordingName(index.Get()));
|
|
return name;
|
|
}
|
|
#endif
|
|
|
|
|
|
// --- CVehicleRecordingStreaming ---------------------------------------------------------------------
|
|
|
|
void CVehicleRecordingStreaming::InitCore()
|
|
{
|
|
HashKey.Clear();
|
|
AudioHashKey.Clear();
|
|
|
|
pRecordingData = NULL;
|
|
RecordingDataSize = 0;
|
|
m_NumTimesUsed = 0;
|
|
pRecordingRsc = NULL;
|
|
}
|
|
|
|
void CVehicleRecordingStreaming::RegisterRecordingFile(const char *pFilename)
|
|
{
|
|
HashKey = pFilename;
|
|
pRecordingData = NULL;
|
|
pRecordingRsc = NULL;
|
|
}
|
|
|
|
void CVehicleRecordingStreaming::Place(CVehicleRecording *pR)
|
|
{
|
|
pRecordingRsc = pR;
|
|
pRecordingData = (u8 *) pR->GetStates();
|
|
RecordingDataSize = pR->GetNumStates() * sizeof(CVehicleStateEachFrame);
|
|
}
|
|
|
|
void CVehicleRecordingStreaming::StartPlayback(u8 **ppPlaybackBuffer, s32 &PlaybackBufferSize)
|
|
{
|
|
*ppPlaybackBuffer = pRecordingData;
|
|
Assertf(pRecordingData, "CVehicleRecordingStreaming::StartPlayback - Recording is played without being streamed in");
|
|
PlaybackBufferSize = RecordingDataSize;
|
|
m_NumTimesUsed++;
|
|
}
|
|
|
|
void CVehicleRecordingStreaming::Remove()
|
|
{
|
|
Assert(pRecordingData);
|
|
|
|
// delete data
|
|
delete pRecordingRsc;
|
|
pRecordingRsc = NULL;
|
|
pRecordingData = NULL;
|
|
RecordingDataSize = 0;
|
|
|
|
Assertf(m_NumTimesUsed == 0, "CVehicleRecordingStreaming::Remove - m_NumTimesUsed = %d for %s so we're just going to set it to 0 now", m_NumTimesUsed, GetRecordingName());
|
|
m_NumTimesUsed = 0; // Is this going to work or will I end up deleting a recording from memory while a vehicle is still reading it?
|
|
}
|
|
|
|
void CVehicleRecordingStreaming::AddRef()
|
|
{
|
|
m_NumTimesUsed++;
|
|
}
|
|
|
|
void CVehicleRecordingStreaming::RemoveRef()
|
|
{
|
|
Assertf(m_NumTimesUsed > 0, "CVehicleRecordingStreaming::RemoveRef - expected m_NumTimesUsed to be greater than 0 but it's %d", m_NumTimesUsed);
|
|
m_NumTimesUsed--;
|
|
}
|
|
|
|
int CVehicleRecordingStreaming::GetNumRefs()
|
|
{
|
|
return m_NumTimesUsed;
|
|
}
|
|
|
|
void CVehicleRecordingStreaming::GetPositionOfCarRecordingAtTime(float time, Vector3 &RetVal)
|
|
{
|
|
Assertf(pRecordingData, "GET_POSITION_OF_CAR_RECORDING_AT_TIME Recording not streamed in?");
|
|
|
|
s32 IndexInRecording = 0;
|
|
s32 IndexToUse = 0;
|
|
while ( (IndexInRecording < RecordingDataSize) && ((CVehicleStateEachFrame *) &(pRecordingData[IndexInRecording]))->TimeInRecording < time)
|
|
{
|
|
IndexToUse = IndexInRecording;
|
|
IndexInRecording += sizeof(CVehicleStateEachFrame);
|
|
}
|
|
|
|
CVehicleRecordingMgr::RestoreInfoForPosition( RetVal, reinterpret_cast<CVehicleStateEachFrame *>(&(pRecordingData[IndexToUse])));
|
|
}
|
|
|
|
void CVehicleRecordingStreaming::GetTransformOfCarRecordingAtTime(float time, Matrix34 &RetVal)
|
|
{
|
|
Assertf(pRecordingData, "GET_POSITION_OF_CAR_RECORDING_AT_TIME Recording not streamed in?");
|
|
|
|
s32 IndexInRecording = 0;
|
|
s32 IndexToUse = 0;
|
|
while ( (IndexInRecording < RecordingDataSize) && ((CVehicleStateEachFrame *) &(pRecordingData[IndexInRecording]))->TimeInRecording < time)
|
|
{
|
|
IndexToUse = IndexInRecording;
|
|
IndexInRecording += sizeof(CVehicleStateEachFrame);
|
|
}
|
|
|
|
CVehicleRecordingMgr::RestoreInfoForMatrix( RetVal, reinterpret_cast<CVehicleStateEachFrame *>(&(pRecordingData[IndexToUse])));
|
|
}
|
|
|
|
float CVehicleRecordingStreaming::GetTotalDurationOfCarRecording()
|
|
{
|
|
Assertf(pRecordingData, "GET_TOTAL_DURATION_OF_CAR_RECORDING Recording not streamed in?");
|
|
|
|
return (float)((CVehicleStateEachFrame *) &(pRecordingData[RecordingDataSize-sizeof(CVehicleStateEachFrame)] ))->TimeInRecording;
|
|
}
|
|
|
|
void CVehicleRecordingStreaming::Invalidate()
|
|
{
|
|
Assert(!pRecordingData);
|
|
HashKey.Clear();
|
|
AudioHashKey.Clear();
|
|
}
|
|
|
|
// --- CVehicleRecordingMgr::CRecording ----------------------------------------------------------
|
|
|
|
#if !__FINAL
|
|
|
|
void CVehicleRecordingMgr::CRecording::Init()
|
|
{
|
|
m_pVehicle = NULL;
|
|
m_pRecordingBuffer = NULL;
|
|
m_isRecording = false;
|
|
}
|
|
|
|
bool CVehicleRecordingMgr::CRecording::StartRecording(CVehicle* pVehicle, int fileNumber, const char* pRecordingName)
|
|
{
|
|
Assert(!m_pRecordingBuffer);
|
|
|
|
|
|
m_pVehicle = pVehicle;
|
|
m_fileNumber = fileNumber;
|
|
m_fileName = pRecordingName;
|
|
|
|
m_flushToDisk = true;
|
|
m_fileHandle = OpenRecordingFile(fileNumber, pRecordingName);
|
|
Assertf(CFileMgr::IsValidFileHandle(m_fileHandle), "failed to open or create recording file, is file checked out?");
|
|
if(CFileMgr::IsValidFileHandle(m_fileHandle) == false)
|
|
return false;
|
|
m_pRecordingBuffer = rage_new u8 [CAR_RECORDING_FLUSH_BUFFER_SIZE];
|
|
Assert(m_pRecordingBuffer);
|
|
|
|
m_recordingIndex = 0;
|
|
m_startTime = 0;
|
|
m_lastFrameRecordedTime = 0;
|
|
m_lastFrameFlushedTime = 0;
|
|
|
|
m_isFirstFrame = true;
|
|
m_isRecording = true;
|
|
//DisplayMode = RDM_NONE;
|
|
|
|
return true;
|
|
}
|
|
|
|
void CVehicleRecordingMgr::CRecording::StartRecordingTransitionFromPlayback(CVehicle* pVehicle, int fileNumber, const char* pRecordingName, int playbackSlot)
|
|
{
|
|
Assert(!m_pRecordingBuffer);
|
|
|
|
m_pVehicle = pVehicle;
|
|
m_fileNumber = fileNumber;
|
|
m_fileName = pRecordingName;
|
|
m_pRecordingBuffer = rage_new u8 [CAR_RECORDING_BUFFER_SIZE];
|
|
Assert(m_pRecordingBuffer);
|
|
m_flushToDisk = false;
|
|
m_isRecording = true;
|
|
//DisplayMode = RDM_NONE;
|
|
|
|
// Now copy the relevant part of the playback (data up until now) into the new recording
|
|
m_startTime = fwTimer::GetTimeInMilliseconds() - (u32)PlaybackRunningTime[playbackSlot];
|
|
|
|
int t = 0;
|
|
while (t < PlaybackIndex[playbackSlot])
|
|
{
|
|
m_pRecordingBuffer[t] = pPlaybackBuffer[playbackSlot][t];
|
|
t++;
|
|
}
|
|
m_recordingIndex = PlaybackIndex[playbackSlot];
|
|
|
|
m_isFirstFrame = (m_recordingIndex == 0);
|
|
|
|
m_lastFrameRecordedTime = 0; // This will force the next frame to be recorded (which is good)
|
|
m_lastFrameFlushedTime = 0;
|
|
}
|
|
|
|
void CVehicleRecordingMgr::CRecording::StopRecording(u8 **ppBuffer, int *pSize)
|
|
{
|
|
Assert(m_pVehicle);
|
|
Assert(m_pRecordingBuffer);
|
|
|
|
if ( m_flushToDisk )
|
|
{
|
|
Assertf( ppBuffer == NULL || *ppBuffer == NULL,
|
|
"This recording was flushed to disk and kicked out of memory. Cannot get the data buffer for it." );
|
|
|
|
WriteBufferToFile(m_fileHandle, m_pRecordingBuffer, m_recordingIndex);
|
|
|
|
CFileMgr::CloseFile( m_fileHandle );
|
|
m_fileHandle = INVALID_FILE_HANDLE;
|
|
delete [] m_pRecordingBuffer;
|
|
m_pRecordingBuffer = NULL;
|
|
}
|
|
else
|
|
{
|
|
if (ppBuffer) // This is debug. The user may want to rescue the buffer to playback straight away
|
|
{
|
|
*ppBuffer = m_pRecordingBuffer;
|
|
*pSize = m_recordingIndex;
|
|
}
|
|
else
|
|
{
|
|
SaveToFile( m_pRecordingBuffer, m_recordingIndex, m_fileNumber, m_fileName);
|
|
delete [] m_pRecordingBuffer;
|
|
}
|
|
}
|
|
|
|
// Restore everything to the original state
|
|
m_isRecording = false;
|
|
m_pVehicle = NULL;
|
|
m_pRecordingBuffer = NULL;
|
|
}
|
|
|
|
void CVehicleRecordingMgr::CRecording::SaveFrame()
|
|
{
|
|
Assert(m_pVehicle);
|
|
|
|
u32 timeInMilliseconds = fwTimer::GetTimeInMilliseconds();
|
|
|
|
const s32 bufferSize = m_flushToDisk ? CAR_RECORDING_FLUSH_BUFFER_SIZE : CAR_RECORDING_BUFFER_SIZE;
|
|
|
|
if (timeInMilliseconds - m_lastFrameRecordedTime > FRAMERECORDPERIOD || m_lastFrameRecordedTime == 0) //force a recording if this is the first frame
|
|
{
|
|
m_lastFrameRecordedTime = timeInMilliseconds;
|
|
|
|
if (m_recordingIndex < bufferSize - static_cast<s32>(sizeof(CVehicleStateEachFrame)))
|
|
{
|
|
CVehicleStateEachFrame *pVehState = (CVehicleStateEachFrame *) &(m_pRecordingBuffer[m_recordingIndex]);
|
|
|
|
// If this is the first frame of the recording we have to set the time correctly.
|
|
if (m_isFirstFrame)
|
|
{
|
|
Assert( m_lastFrameFlushedTime == 0 );
|
|
|
|
m_startTime = timeInMilliseconds;
|
|
m_lastFrameFlushedTime = timeInMilliseconds;
|
|
pVehState->TimeInRecording = 0;
|
|
|
|
m_isFirstFrame = false;
|
|
}
|
|
else
|
|
{
|
|
pVehState->TimeInRecording = timeInMilliseconds - m_startTime;
|
|
}
|
|
|
|
StoreInfoForCar(m_pVehicle, pVehState);
|
|
m_recordingIndex += sizeof(CVehicleStateEachFrame);
|
|
}
|
|
else
|
|
{
|
|
Displayf("Buffer full. No more carpositions are being recorded\n");
|
|
|
|
StopRecordingCar();
|
|
}
|
|
}
|
|
|
|
if ( m_flushToDisk && timeInMilliseconds - m_lastFrameFlushedTime > RECORDFLUSHPERIOD )
|
|
{
|
|
WriteBufferToFile( m_fileHandle, m_pRecordingBuffer, m_recordingIndex );
|
|
m_recordingIndex = 0;
|
|
m_lastFrameFlushedTime = timeInMilliseconds;
|
|
}
|
|
|
|
#if __BANK && __STATS
|
|
const phCollider* pCollider = m_pVehicle->GetCollider();
|
|
const Matrix34 vehicleMatrix = MAT34V_TO_MATRIX34(pCollider ? pCollider->GetMatrix() : m_pVehicle->GetMatrix());
|
|
|
|
if(!g_VehiclePositionOnPreviousUpdate.IsClose(VEC3_MAX, SMALL_FLOAT))
|
|
{
|
|
#if __STATS
|
|
const float effectiveSpeed = vehicleMatrix.d.Dist(g_VehiclePositionOnPreviousUpdate) * fwTimer::GetInvTimeStep();
|
|
|
|
PF_SET(vehicleEffectiveSpeed, effectiveSpeed);
|
|
|
|
const Vector3& velocity = m_pVehicle->GetVelocity();
|
|
const float actualSpeed = velocity.Mag();
|
|
|
|
PF_SET(vehicleActualSpeed, actualSpeed);
|
|
#endif // __STATS
|
|
}
|
|
|
|
g_VehiclePositionOnPreviousUpdate = vehicleMatrix.d;
|
|
#endif // __BANK
|
|
}
|
|
|
|
float CVehicleRecordingMgr::CRecording::FindTimePositionInRecording()
|
|
{
|
|
return (float)(fwTimer::GetTimeInMilliseconds() - m_startTime);
|
|
}
|
|
|
|
void CVehicleRecordingMgr::CRecording::WriteBufferToFile(FileHandle fid, u8* buffer, s32 length)
|
|
{
|
|
if ( Verifyf(CFileMgr::IsValidFileHandle(fid), "Vehicle recording file handle is invalid") )
|
|
{
|
|
#if __BE
|
|
EndianSwapRecording( buffer, length );
|
|
#endif
|
|
|
|
ASSERT_ONLY(s32 numBytesWritten =) CFileMgr::Write( fid, (char*) buffer, length );
|
|
Assert( numBytesWritten == length );
|
|
|
|
// Swap endian back. This is only important when using the widgets to record, save and playback a recording. (The data should still be usable after the recording)
|
|
#if __BE
|
|
EndianSwapRecording( buffer, length );
|
|
#endif
|
|
}
|
|
|
|
}
|
|
|
|
void CVehicleRecordingMgr::CRecording::SaveToFile(u8 *pBuffer, int length, int fileNumber, const char* pRecordingName)
|
|
{
|
|
FileHandle fid = OpenRecordingFile( fileNumber, pRecordingName);
|
|
WriteBufferToFile( fid, pBuffer, length );
|
|
CFileMgr::CloseFile( fid );
|
|
}
|
|
|
|
FileHandle CVehicleRecordingMgr::CRecording::OpenRecordingFile(const s32 fileNumber, const char* pRecordingName)
|
|
{
|
|
FileHandle fid;
|
|
char fileName[50];
|
|
|
|
// Open a file and save out the data.
|
|
const char *PathForCarRecordings = 0;
|
|
if(PARAM_vehrecordpath.Get(PathForCarRecordings))
|
|
{
|
|
CFileMgr::SetDir(PathForCarRecordings);
|
|
}
|
|
else
|
|
{
|
|
CFileMgr::SetDir(DEFAULT_RECORDING_PATH);
|
|
}
|
|
|
|
sprintf(fileName, "%s%03d.ivr", pRecordingName, fileNumber);
|
|
fid = CFileMgr::OpenFileForWriting(fileName);
|
|
CFileMgr::SetDir("");
|
|
|
|
return fid;
|
|
}
|
|
|
|
void CVehicleRecordingMgr::CRecording::LoadFromFile(u8 **ppBuffer, int *pLength, int fileNumber, const char* pRecordingName)
|
|
{
|
|
FileHandle fid;
|
|
s32 NumBytes=0;
|
|
char fileName[50];
|
|
|
|
// Open a file and load the data.
|
|
const char *PathForCarRecordings = 0;
|
|
if(PARAM_vehrecordpath.Get(PathForCarRecordings))
|
|
{
|
|
CFileMgr::SetDir(PathForCarRecordings);
|
|
}
|
|
else
|
|
{
|
|
CFileMgr::SetDir(DEFAULT_RECORDING_PATH);
|
|
}
|
|
|
|
sprintf(fileName, "%s%03d.ivr", pRecordingName, fileNumber);
|
|
fid = CFileMgr::OpenFile(fileName);
|
|
if ( CFileMgr::IsValidFileHandle(fid) )
|
|
{
|
|
*ppBuffer = rage_new u8 [CAR_RECORDING_BUFFER_SIZE];
|
|
|
|
if (*ppBuffer)
|
|
{
|
|
NumBytes = CFileMgr::Read(fid, (char *)*ppBuffer, 9999999);
|
|
Assert(NumBytes > 0);
|
|
|
|
#if __BE
|
|
EndianSwapRecording(*ppBuffer, NumBytes);
|
|
#endif
|
|
}
|
|
CFileMgr::CloseFile(fid);
|
|
}
|
|
CFileMgr::SetDir("");
|
|
*pLength = NumBytes;
|
|
}
|
|
|
|
void CVehicleRecordingMgr::CRecording::EndianSwapRecording(u8 *pBuffer, s32 dataSize)
|
|
{
|
|
Assert(pBuffer);
|
|
|
|
s32 IndexInArray = 0;
|
|
while (IndexInArray < dataSize)
|
|
{
|
|
CVehicleStateEachFrame *pState = (CVehicleStateEachFrame *) &(pBuffer[IndexInArray]);
|
|
pState->EndianSwap();
|
|
IndexInArray += sizeof(CVehicleStateEachFrame);
|
|
}
|
|
}
|
|
|
|
void CVehicleRecordingMgr::CRecording::ShiftRecording(s32 RecordingNumber, const char* pRecordingName, float ShiftByX, float ShiftByY, float ShiftByZ)
|
|
{
|
|
FileHandle fileIn;
|
|
FileHandle fileOut;
|
|
char FileNameIn[32];
|
|
char FileNameOut[32];
|
|
CVehicleStateEachFrame VehState;
|
|
#if __ASSERT
|
|
s32 BytesLoaded;
|
|
#endif
|
|
|
|
CFileMgr::SetDir("x:/gta5/assets/recordings/car");
|
|
sprintf(FileNameIn, "%s%03d.ivr", pRecordingName, RecordingNumber);
|
|
sprintf(FileNameOut, "%s%03d.ivrshifter", pRecordingName, RecordingNumber);
|
|
fileIn = CFileMgr::OpenFile(FileNameIn);
|
|
if (CFileMgr::IsValidFileHandle(fileIn))
|
|
{ // File exists.
|
|
fileOut = CFileMgr::OpenFileForWriting(FileNameOut);
|
|
Assertf(CFileMgr::IsValidFileHandle(fileOut), "%s:Could not open file", FileNameOut);
|
|
|
|
while( (ASSERT_ONLY(BytesLoaded =) CFileMgr::Read(fileIn, (char *)&VehState, sizeof(CVehicleStateEachFrame))) != 0)
|
|
{
|
|
Assert(BytesLoaded == sizeof(CVehicleStateEachFrame));
|
|
#if __BE
|
|
EndianSwapRecording((u8 *)&VehState, sizeof(CVehicleStateEachFrame));
|
|
#endif
|
|
|
|
VehState.CoorsX += ShiftByX;
|
|
VehState.CoorsY += ShiftByY;
|
|
VehState.CoorsZ += ShiftByZ;
|
|
#if __BE
|
|
EndianSwapRecording((u8 *)&VehState, sizeof(CVehicleStateEachFrame));
|
|
#endif
|
|
CFileMgr::Write(fileOut, (char *)&VehState, sizeof(CVehicleStateEachFrame));
|
|
}
|
|
|
|
CFileMgr::CloseFile(fileOut);
|
|
CFileMgr::CloseFile(fileIn);
|
|
}
|
|
CFileMgr::SetDir("");
|
|
}
|
|
|
|
void CVehicleRecordingMgr::CRecording::FixRecordingTimeStamps(s32 RecordingNumber, const char* pRecordingName)
|
|
{
|
|
FileHandle fileIn;
|
|
FileHandle fileOut;
|
|
char FileNameIn[32];
|
|
char FileNameOut[32];
|
|
|
|
CFileMgr::SetDir("x:/gta5/assets/recordings/car");
|
|
sprintf(FileNameIn, "%s%03d.ivr", pRecordingName, RecordingNumber);
|
|
sprintf(FileNameOut, "%s%03d.ivrfixed", pRecordingName, RecordingNumber);
|
|
fileIn = CFileMgr::OpenFile(FileNameIn);
|
|
if (CFileMgr::IsValidFileHandle(fileIn))
|
|
{ // File exists.
|
|
fileOut = CFileMgr::OpenFileForWriting(FileNameOut);
|
|
Assertf(CFileMgr::IsValidFileHandle(fileOut), "%s:Could not open file", FileNameOut);
|
|
|
|
CVehicleStateEachFrame vehicleStates[g_NumStatesToFixUpBeforePinningTimeStamp];
|
|
s32 BytesLoaded;
|
|
|
|
Vector3 previousPosition(VEC3_MAX);
|
|
Vector3 previousVelocity(VEC3_MAX);
|
|
u32 previousTimeInRecordingBeforeFixUp = (u32)-1;
|
|
u32 previousTimeInRecordingAfterFixedUp = (u32)-1;
|
|
|
|
const u32 numBytesToLoad = g_NumStatesToFixUpBeforePinningTimeStamp * sizeof(CVehicleStateEachFrame);
|
|
|
|
while((BytesLoaded = CFileMgr::Read(fileIn, (char *)vehicleStates, numBytesToLoad)) != 0)
|
|
{
|
|
#if __BE
|
|
EndianSwapRecording((u8 *)vehicleStates, BytesLoaded);
|
|
#endif
|
|
|
|
const u32 numStatesLoaded = Min((u32)BytesLoaded / (u32)sizeof(CVehicleStateEachFrame), g_NumStatesToFixUpBeforePinningTimeStamp);
|
|
|
|
for(u32 stateIndex=0; stateIndex<numStatesLoaded; stateIndex++)
|
|
{
|
|
CVehicleStateEachFrame& currentState = vehicleStates[stateIndex];
|
|
|
|
const u32 timeInRecording = currentState.TimeInRecording;
|
|
const Vector3 position(currentState.GetCoors());
|
|
const Vector3 velocity(currentState.GetSpeedX(), currentState.GetSpeedY(), currentState.GetSpeedZ());
|
|
|
|
if(previousTimeInRecordingBeforeFixUp != (u32)-1)
|
|
{
|
|
Vector3 averageVelocity;
|
|
averageVelocity.Lerp(0.5f, previousVelocity, velocity);
|
|
|
|
const float averageSpeed = averageVelocity.Mag();
|
|
if(averageSpeed >= g_MinVehicleSpeedToFixUp)
|
|
{
|
|
const float distanceTraveled = position.Dist(previousPosition);
|
|
const u32 desiredTimeDelta = (u32)Floorf(distanceTraveled * 1000.0f / averageSpeed);
|
|
const u32 currentTimeDelta = timeInRecording - previousTimeInRecordingBeforeFixUp;
|
|
//Ensure that we don't attempt an overzealous fix-up and that the time-stamps cannot reverse direction.
|
|
const u32 minTimeDelta = (u32)Max((s32)currentTimeDelta - (s32)g_MaxVehicleTimeStampFixUpInMilliseconds, 0);
|
|
const u32 timeDeltaToApply = Clamp(desiredTimeDelta, minTimeDelta, currentTimeDelta + g_MaxVehicleTimeStampFixUpInMilliseconds);
|
|
|
|
const u32 desiredTimeInRecording = (previousTimeInRecordingAfterFixedUp + timeDeltaToApply);
|
|
|
|
Displayf("Fix-up: %04d (Before limiting: %04d)", (s32)timeDeltaToApply - (s32)currentTimeDelta, (s32)desiredTimeDelta - (s32)currentTimeDelta);
|
|
|
|
if(stateIndex == (g_NumStatesToFixUpBeforePinningTimeStamp - 1))
|
|
{
|
|
//This is the final state in the block, so we must pin the time-stamp and compensate for the error in the preceding states.
|
|
const s32 totalErrorToDistribute = (s32)currentState.TimeInRecording - (s32)desiredTimeInRecording;
|
|
|
|
Displayf("Pinning error: %04d", totalErrorToDistribute);
|
|
|
|
for(u32 pinStateIndex=0; pinStateIndex<(g_NumStatesToFixUpBeforePinningTimeStamp - 1); pinStateIndex++)
|
|
{
|
|
const float errorRatioToApply = (float)(pinStateIndex + 1) / (float)g_NumStatesToFixUpBeforePinningTimeStamp;
|
|
|
|
const s32 errorToApply = (s32)Floorf(errorRatioToApply * (float)totalErrorToDistribute);
|
|
vehicleStates[pinStateIndex].TimeInRecording = (u32)Max((s32)vehicleStates[pinStateIndex].TimeInRecording + errorToApply, 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
currentState.TimeInRecording = desiredTimeInRecording;
|
|
}
|
|
}
|
|
}
|
|
|
|
previousPosition = position;
|
|
previousVelocity = velocity;
|
|
previousTimeInRecordingBeforeFixUp = timeInRecording;
|
|
previousTimeInRecordingAfterFixedUp = currentState.TimeInRecording;
|
|
}
|
|
|
|
#if __BE
|
|
EndianSwapRecording((u8 *)vehicleStates, BytesLoaded);
|
|
#endif
|
|
|
|
CFileMgr::Write(fileOut, (char *)vehicleStates, BytesLoaded);
|
|
}
|
|
|
|
CFileMgr::CloseFile(fileOut);
|
|
CFileMgr::CloseFile(fileIn);
|
|
}
|
|
CFileMgr::SetDir("");
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : StoreInfoForMatrix()
|
|
// PURPOSE : Compresses the matrix for a car
|
|
// RETURNS :
|
|
// PARAMETERS :
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
void CVehicleRecordingMgr::CRecording::StoreInfoForMatrix(const Matrix34 *pMatrix, CVehicleStateEachFrame *pCarState)
|
|
{
|
|
pCarState->Matrix_a_x = (s8)(pMatrix->a.x * 127);
|
|
pCarState->Matrix_a_y = (s8)(pMatrix->a.y * 127);
|
|
pCarState->Matrix_a_z = (s8)(pMatrix->a.z * 127);
|
|
pCarState->Matrix_b_x = (s8)(pMatrix->b.x * 127);
|
|
pCarState->Matrix_b_y = (s8)(pMatrix->b.y * 127);
|
|
pCarState->Matrix_b_z = (s8)(pMatrix->b.z * 127);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : StoreInfoForCar()
|
|
// PURPOSE : Compresses the matrix and stuff for a car to be played back
|
|
// in an animation.
|
|
// RETURNS :
|
|
// PARAMETERS :
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
void CVehicleRecordingMgr::CRecording::StoreInfoForCar(CVehicle *pCar, CVehicleStateEachFrame *pCarState)
|
|
{
|
|
Matrix34 m = MAT34V_TO_MATRIX34(pCar->GetMatrix());
|
|
StoreInfoForMatrix( &m, pCarState);
|
|
|
|
const Vector3 vCarPosition = VEC3V_TO_VECTOR3(pCar->GetVehiclePosition());
|
|
pCarState->CoorsX = vCarPosition.x;
|
|
pCarState->CoorsY = vCarPosition.y;
|
|
pCarState->CoorsZ = vCarPosition.z;
|
|
|
|
pCarState->SetSpeedX(pCar->GetVelocity().x);
|
|
pCarState->SetSpeedY(pCar->GetVelocity().y);
|
|
pCarState->SetSpeedZ(pCar->GetVelocity().z);
|
|
|
|
pCarState->SteerAngle = (s8)(pCar->m_vehControls.m_steerAngle * 20.0f);
|
|
pCarState->Gas = (s8)(pCar->m_vehControls.m_throttle * 100.0f);
|
|
pCarState->Brake = (s8)(pCar->m_vehControls.m_brake * 100.0f);
|
|
pCarState->HandBrake = pCar->m_vehControls.m_handBrake;
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
// --- CVehicleRecordingMgr ---------------------------------------------------------------------
|
|
|
|
//
|
|
// name: CVehicleRecordingMgr::InitialiseData
|
|
// description:
|
|
void CVehicleRecordingMgr::InitialiseData()
|
|
{
|
|
s32 C;
|
|
|
|
#if !__FINAL
|
|
for (s32 Recording = 0; Recording < MAX_CARS_RECORDED_NUMBER; Recording++)
|
|
{
|
|
ms_vehiclesRecordings[Recording].Init();
|
|
}
|
|
#endif
|
|
|
|
for (C = 0; C < MAX_CARS_PLAYBACK_NUMBER; C++)
|
|
{
|
|
SetVehicleForPlayback(C, NULL);
|
|
PlaybackAdditionalRotation[C] = Vec3V(V_ZERO);
|
|
PlaybackPreviousAdditionalRotation[C] = Vec3V(V_ZERO);
|
|
PlaybackLocalPositionOffset[C] = Vec3V(V_ZERO);
|
|
PlaybackGlobalPositionOffset[C] = Vec3V(V_ZERO);
|
|
pPlaybackBuffer[C] = NULL;
|
|
bPlaybackGoingOn[C] = false;
|
|
bPlaybackPaused[C] = false;
|
|
// PlaybackForCodeIndex[C] = -1;
|
|
}
|
|
|
|
sm_NumPlayBackFiles = 0;//reset the number of playback files to 0 as they have all been cleared.
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : Init()
|
|
// PURPOSE : Initialises this whole recording stuff. If we want to change
|
|
// what happens in the game (record / playback / normal) we can
|
|
// change it here.
|
|
// RETURNS :
|
|
// PARAMETERS :
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
void CVehicleRecordingMgr::Init(unsigned initMode)
|
|
{
|
|
USE_MEMBUCKET(MEMBUCKET_SCRIPT);
|
|
if(initMode == INIT_CORE)
|
|
{
|
|
Assert(!sm_StreamingArray);
|
|
sm_StreamingArray = rage_new CVehicleRecordingStreaming[GetMaxNumOfPlaybackFiles()];
|
|
|
|
for (s32 C = 0; C < MAX_CARS_PLAYBACK_NUMBER; C++)
|
|
{
|
|
pVehicleForPlayback[C] = NULL;
|
|
}
|
|
InitialiseData();
|
|
|
|
const int cnt = GetMaxNumOfPlaybackFiles();
|
|
for (s32 loop = 0; loop < cnt; loop++)
|
|
{
|
|
sm_StreamingArray[loop].InitCore();
|
|
}
|
|
}
|
|
else if (initMode == INIT_SESSION)
|
|
{
|
|
if (!sm_RecordInfoMap.IsInitialized())
|
|
{
|
|
sm_RecordInfoMap.Init(GetMaxNumOfPlaybackFiles());
|
|
}
|
|
}
|
|
else if (initMode == INIT_BEFORE_MAP_LOADED)
|
|
{
|
|
if (!sm_RecordInfoMap.IsInitialized())
|
|
{
|
|
sm_RecordInfoMap.Init(GetMaxNumOfPlaybackFiles());
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : GetStreamingModuleId()
|
|
// PURPOSE :
|
|
// RETURNS :
|
|
// PARAMETERS :
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
strStreamingModule* CVehicleRecordingMgr::GetStreamingModule()
|
|
{
|
|
// streaming module
|
|
static CVehicleRecordingStreamingModule s_VehicleRecordingModule;
|
|
return &s_VehicleRecordingModule;
|
|
}
|
|
|
|
|
|
void* CVehicleRecordingMgr::GetPtr(s32 index)
|
|
{
|
|
return sm_StreamingArray[index].GetRecordingRsc();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : ShutdownLevel()
|
|
// PURPOSE : Frees the bits of streaming memory we might have.
|
|
// RETURNS :
|
|
// PARAMETERS :
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
void CVehicleRecordingMgr::Shutdown(unsigned shutdownMode)
|
|
{
|
|
if(shutdownMode == SHUTDOWN_WITH_MAP_LOADED)
|
|
{
|
|
sm_RecordInfoMap.Reset();
|
|
}
|
|
else if(shutdownMode == SHUTDOWN_SESSION)
|
|
{
|
|
const int maxNumOfPlaybackFiles = GetMaxNumOfPlaybackFiles();
|
|
for (s32 C = 0; C < maxNumOfPlaybackFiles; C++)
|
|
{
|
|
if (sm_StreamingArray[C].HasRecordingData())
|
|
{
|
|
CVehicleRecordingMgr::GetStreamingModule()->ClearRequiredFlag(C, STRFLAG_MISSION_REQUIRED);
|
|
CVehicleRecordingMgr::GetStreamingModule()->StreamingRemove(strLocalIndex(C));
|
|
}
|
|
else
|
|
{
|
|
Assertf(GetNumRefs(strLocalIndex(C)) == 0, "CVehicleRecordingMgr::Shutdown - vehicle recording %s has no recording data but still has %d refs", sm_StreamingArray[C].GetRecordingName(), GetNumRefs(strLocalIndex(C)));
|
|
}
|
|
}
|
|
|
|
InitialiseData();
|
|
}
|
|
else if(shutdownMode == SHUTDOWN_CORE)
|
|
{
|
|
Assert(sm_StreamingArray);
|
|
delete []sm_StreamingArray;
|
|
sm_StreamingArray = NULL;
|
|
}
|
|
}
|
|
|
|
//
|
|
// name: CVehicleRecordingMgr::SetVehicleForPlayback
|
|
// description:
|
|
void CVehicleRecordingMgr::SetVehicleForPlayback(int SlotNumber, CVehicle *pNewVehicle)
|
|
{
|
|
Assertf( (SlotNumber >= 0) && (SlotNumber < MAX_CARS_PLAYBACK_NUMBER), "CVehicleRecordingMgr::SetVehicleForPlayback - SlotNumber is out of range");
|
|
if ((SlotNumber >= 0) && (SlotNumber < MAX_CARS_PLAYBACK_NUMBER))
|
|
{
|
|
pVehicleForPlayback[SlotNumber] = pNewVehicle;
|
|
}
|
|
}
|
|
|
|
#if !__FINAL
|
|
|
|
bool CVehicleRecordingMgr::DoesRecordingFileExist(const s32 fileNumber, const char* pRecordingName)
|
|
{
|
|
char fileName[50];
|
|
|
|
// Open a file and save out the data.
|
|
const char *PathForCarRecordings = 0;
|
|
if(PARAM_vehrecordpath.Get(PathForCarRecordings))
|
|
{
|
|
CFileMgr::SetDir(PathForCarRecordings);
|
|
}
|
|
else
|
|
{
|
|
CFileMgr::SetDir(DEFAULT_RECORDING_PATH);
|
|
}
|
|
|
|
sprintf(fileName, "%s%03d.ivr", pRecordingName, fileNumber);
|
|
|
|
|
|
FileHandle temp_file_handle = CFileMgr::OpenFileForAppending(fileName);
|
|
CFileMgr::SetDir("");
|
|
|
|
if(temp_file_handle != INVALID_FILE_HANDLE)
|
|
{
|
|
Warningf("Recording file already exists %s", fileName);
|
|
CFileMgr::CloseFile(temp_file_handle);
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : StartRecordingCar()
|
|
// PURPOSE : Once this is called the code will record the position of this vehicle every
|
|
// frame. A file is saved out when StopRecordingCar is called.
|
|
// RETURNS :false if command fails
|
|
// PARAMETERS :
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
bool CVehicleRecordingMgr::StartRecordingCar(class CVehicle *pCar, u32 FileNum, const char* pRecordingName, bool allowOverwrite /*= false*/)
|
|
{
|
|
s32 C, FreeRecording = -1;
|
|
Assert(pCar);
|
|
Assertf(FileNum < HIGHESTFILENUMALLOWED, "Recording number should be < 4000");
|
|
|
|
if(allowOverwrite == false)//check whether the file already exits
|
|
{
|
|
if(CVehicleRecordingMgr::DoesRecordingFileExist(FileNum, pRecordingName))
|
|
{
|
|
return false;//file already exits so don't record
|
|
}
|
|
}
|
|
|
|
for (C = 0; C < MAX_CARS_RECORDED_NUMBER; C++)
|
|
{
|
|
if (!ms_vehiclesRecordings[C].IsRecording())
|
|
{
|
|
FreeRecording = C;
|
|
}
|
|
}
|
|
|
|
Assert(FreeRecording >= 0);
|
|
|
|
return ms_vehiclesRecordings[FreeRecording].StartRecording(pCar, FileNum, pRecordingName);
|
|
}
|
|
|
|
// This starts a recording on a vehicle that has a playback running on it.
|
|
// The data from the recording being played back is copied up until the moment
|
|
// this function is called.
|
|
// Can be used to playback a recording and switch to recording when a collision happens
|
|
// without loss of quality.
|
|
|
|
//returns false if command fails
|
|
bool CVehicleRecordingMgr::StartRecordingCarTransitionFromPlayback(class CVehicle *pCar, u32 FileNum, const char* pRecordingName, bool allowOverwrite /* = false*/)
|
|
{
|
|
s32 C, FreeRecording = -1;
|
|
Assert(pCar);
|
|
Assertf(FileNum < HIGHESTFILENUMALLOWED, "Recording number should be < 4000");
|
|
|
|
if(allowOverwrite == false)//check whether the file already exits
|
|
{
|
|
if(CVehicleRecordingMgr::DoesRecordingFileExist(FileNum, pRecordingName))
|
|
{
|
|
return false;//file already exits so dont record
|
|
}
|
|
}
|
|
|
|
|
|
s32 playbackRecording;
|
|
playbackRecording = 0;
|
|
while (playbackRecording < MAX_CARS_PLAYBACK_NUMBER && ( (!bPlaybackGoingOn[playbackRecording]) || pVehicleForPlayback[playbackRecording] != pCar))
|
|
{
|
|
playbackRecording++;
|
|
}
|
|
|
|
Assertf(playbackRecording < MAX_CARS_PLAYBACK_NUMBER, "START_RECORDING_VEHICLE_TRANSITION_FROM_PLAYBACK. Vehicle must have a recording playing on it for this to work");
|
|
if (playbackRecording >= MAX_CARS_PLAYBACK_NUMBER)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// First we need to start a new recording.
|
|
for (C = 0; C < MAX_CARS_RECORDED_NUMBER; C++)
|
|
{
|
|
if (!ms_vehiclesRecordings[C].IsRecording())
|
|
{
|
|
FreeRecording = C;
|
|
}
|
|
}
|
|
|
|
Assert(FreeRecording >= 0);
|
|
|
|
ms_vehiclesRecordings[FreeRecording].StartRecordingTransitionFromPlayback(pCar, FileNum, pRecordingName, playbackRecording);
|
|
// Now stop the playback of the recording as it's not needed anymore.
|
|
StopPlaybackWithIndex(playbackRecording);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : StopRecordingCar()
|
|
// PURPOSE : When this function is called we are done recording and save out a file
|
|
// with the playback info for this car.
|
|
// RETURNS :
|
|
// PARAMETERS :
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
void CVehicleRecordingMgr::StopRecordingCar(CVehicle *pVehToStopRecordingFor, u8 **ppBuffer, int *pSize)
|
|
{
|
|
// Stop the recordings one by one
|
|
for (int Recording = 0; Recording < MAX_CARS_RECORDED_NUMBER; Recording++)
|
|
{
|
|
if (ms_vehiclesRecordings[Recording].IsRecording())
|
|
{
|
|
if (pVehToStopRecordingFor == NULL || pVehToStopRecordingFor == ms_vehiclesRecordings[Recording].GetVehicle())
|
|
{
|
|
ms_vehiclesRecordings[Recording].StopRecording(ppBuffer, pSize);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : IsCarBeingRecorded(class CVehicle *pCar)
|
|
// PURPOSE : Used by the level designers to find out if a car is being recorded.
|
|
// RETURNS : true/false
|
|
// PARAMETERS : pointer to the car in question
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
bool CVehicleRecordingMgr::IsCarBeingRecorded(const CVehicle *pCar)
|
|
{
|
|
s32 Recording;
|
|
Assert(pCar);
|
|
|
|
for (Recording = 0; Recording < MAX_CARS_RECORDED_NUMBER; Recording++)
|
|
{
|
|
if((ms_vehiclesRecordings[Recording].GetVehicle() == pCar) && ms_vehiclesRecordings[Recording].IsRecording())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// bool CVehicleRecordingMgr::IsAnyCarBeingRecorded()
|
|
// {
|
|
// s32 Recording;
|
|
//
|
|
// for (Recording = 0; Recording < MAX_CARS_RECORDED_NUMBER; Recording++)
|
|
// {
|
|
// if(ms_vehiclesRecordings[Recording].IsRecording())
|
|
// {
|
|
// return true;
|
|
// }
|
|
// }
|
|
// return false;
|
|
// }
|
|
#endif
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : StartPlaybackRecordedCar()
|
|
// PURPOSE : This will start a playback for this car. The file is opened. Data is read in
|
|
// and from this point on the coordinates of this car are overwritten with data
|
|
// from the file.
|
|
// RETURNS :
|
|
// PARAMETERS :
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
void CVehicleRecordingMgr::StartPlaybackRecordedCar(class CVehicle *pCar, int index, bool bArgUseCarAI, float fSpeed, s32 iDrivingFlags, bool bLooped, s32 ForCodeIndex, u8 *pData, int dataSize, s32 iVehicleRecordingFlags, u32 delayUntilTurningBackOnAIActivation )
|
|
{
|
|
s32 C;
|
|
|
|
if (pData)
|
|
{
|
|
index = -1;
|
|
}
|
|
else if(index == -1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Assert(ForCodeIndex < FOR_CODE_INDEX_LAST);
|
|
Assert(pCar || ForCodeIndex >= 0);
|
|
// Assert(pCar==NULL || !pCar->IsDummy()); // Make sure the car is not a dummy when we start playing. This could mess things up.
|
|
|
|
// Obbe's fix to makes sure car isn't a dummy when we start a recording.
|
|
if (pCar && pCar->IsDummy())
|
|
{
|
|
pCar->TryToMakeFromDummyIncludingParents(true);
|
|
Assert(!pCar->IsDummy());
|
|
}
|
|
|
|
if (ForCodeIndex >= 0)
|
|
{
|
|
C = ForCodeIndex; // First slots are reserved for code controlled vehicles.
|
|
Assertf(!pVehicleForPlayback[C] && !bPlaybackGoingOn[C], "This slot should really be free");
|
|
}
|
|
else
|
|
{
|
|
C = FOR_CODE_INDEX_LAST;
|
|
while (C < MAX_CARS_PLAYBACK_NUMBER && bPlaybackGoingOn[C])
|
|
{
|
|
Assertf( (pVehicleForPlayback[C] != pCar) || (pCar == NULL), "START_PLAYBACK_RECORDED_CAR - Attempting to play two recordings on the same car");
|
|
C++;
|
|
}
|
|
}
|
|
|
|
if(!AssertVerify(C < MAX_CARS_PLAYBACK_NUMBER))
|
|
{
|
|
Warningf("StartPlaybackRecordedCar: Too many cars played back simultanuously");
|
|
}
|
|
|
|
// Make sure data is streamed in.
|
|
if(!Verifyf(index == -1 || sm_StreamingArray[index].HasRecordingData(), "START_PLAYBACK_RECORDED_CAR - recording %s not loaded", sm_StreamingArray[index].GetRecordingName()))
|
|
{
|
|
return;
|
|
}
|
|
|
|
SetVehicleForPlayback(C, pCar);
|
|
PlaybackAdditionalRotation[C] = Vec3V(V_ZERO);
|
|
PlaybackPreviousAdditionalRotation[C] = Vec3V(V_ZERO);
|
|
PlaybackLocalPositionOffset[C] = Vec3V(V_ZERO);
|
|
PlaybackGlobalPositionOffset[C] = Vec3V(V_ZERO);
|
|
PlaybackIndex[C] = 0;
|
|
PlaybackRunningTime[C] = 0.0f;
|
|
PlaybackSpeed[C] = 1.0f; // Standard speed (same as recording)
|
|
bPlaybackLooped[C] = bLooped;
|
|
Assert(!pPlaybackBuffer[C]);
|
|
PlayBackStreamingIndex[C] = index;
|
|
if (index >= 0)
|
|
{
|
|
sm_StreamingArray[index].StartPlayback(&(pPlaybackBuffer[C]), PlaybackBufferSize[C]);
|
|
}
|
|
else
|
|
{
|
|
pPlaybackBuffer[C] = pData;
|
|
PlaybackBufferSize[C] = dataSize;
|
|
}
|
|
|
|
Assert(pPlaybackBuffer[C]);
|
|
|
|
pCar->GetPortalTracker()->SetProbeType(CPortalTracker::PROBE_TYPE_NEAR);
|
|
pCar->GetPortalTracker()->RequestRescanNextUpdate();
|
|
|
|
bPlaybackGoingOn[C] = true;
|
|
bPlaybackPaused[C] = false;
|
|
bUseCarAI[C] = bArgUseCarAI;
|
|
aiVechicleRecordingFlags[C] = iVehicleRecordingFlags | VRF_FirstUpdate;
|
|
aiDelayUntilRevertingBack[C] = delayUntilTurningBackOnAIActivation;
|
|
TurnBackIntoFullPlayBackTime[C] = 0; // We're not interested in turning this into a full (non ai) recording.
|
|
// PlaybackForCodeIndex[C] = ForCodeIndex;
|
|
|
|
// Engine starting
|
|
if( pCar )
|
|
{
|
|
if( iVehicleRecordingFlags & VRF_StartEngineInstantly )
|
|
{
|
|
pCar->SwitchEngineOn(true);
|
|
}
|
|
else if( iVehicleRecordingFlags & VRF_StartEngineWithStartup )
|
|
{
|
|
pCar->SwitchEngineOn(false);
|
|
}
|
|
}
|
|
|
|
if (ForCodeIndex < 0)
|
|
{
|
|
if (bUseCarAI[C])
|
|
{ // Tell this car to follow this path. The AI will take over from here,
|
|
Assert(pVehicleForPlayback[C]);
|
|
CVehicle *pVeh = pVehicleForPlayback[C];
|
|
if(!AssertVerify(pVeh->GetDriver()))
|
|
{
|
|
Warningf("StartPlaybackRecordedCar: Vehicle doesn't have driver - this will play with an empty vehicle");
|
|
}
|
|
CPed *pDriver = pVeh->GetDriver();
|
|
|
|
CTaskControlVehicle* pTask = NULL;
|
|
|
|
sVehicleMissionParams params;
|
|
params.m_fCruiseSpeed = fSpeed;
|
|
params.m_iDrivingFlags = iDrivingFlags;
|
|
CTask* pCarTask = rage_new CTaskVehicleFollowRecording(params);
|
|
|
|
if (pDriver)
|
|
{
|
|
pTask = rage_new CTaskControlVehicle(pVeh, pCarTask);
|
|
|
|
pDriver->GetPedIntelligence()->AddTaskAtPriority(pTask, PED_TASK_PRIORITY_PRIMARY);
|
|
}
|
|
else
|
|
{
|
|
pVeh->GetIntelligence()->AddTask(VEHICLE_TASK_TREE_PRIMARY, pCarTask, VEHICLE_TASK_PRIORITY_PRIMARY);
|
|
}
|
|
|
|
|
|
SetRecordingToPointClosestToCoors(C, VEC3V_TO_VECTOR3(pCar->GetVehiclePosition()));
|
|
}
|
|
else
|
|
{
|
|
MUST_FIX_THIS(sandy - need to use movers or something?);
|
|
// pVehicleForPlayback[C]->m_nPhysicalFlags.bInfiniteMass = true; // don't let recorded car be affected by collisions
|
|
// pVehicleForPlayback[C]->m_nPhysicalFlags.bInfiniteMassFixed = false; // but do affect others with collisions
|
|
}
|
|
}
|
|
/* Some scripts are attaching vehicles in a very odd way,so will need to find a different solution
|
|
// If we're not using AI, then disconnect trailers
|
|
if(!bUseCarAI[C])
|
|
{
|
|
if(pVehicleForPlayback[C] && pVehicleForPlayback[C]->GetCurrentPhysicsInst())
|
|
{
|
|
if(pVehicleForPlayback[C]->GetVehicleType() == VEHICLE_TYPE_TRAILER)
|
|
{
|
|
// Don't let people play car recordings on attached trailers
|
|
CTrailer *pTrailer = static_cast<CTrailer*>(pCar);
|
|
pTrailer->DetachFromParent(0);
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
|
|
if (ForCodeIndex < 0)
|
|
{
|
|
pVehicleForPlayback[C]->GetIntelligence()->SetRecordingNumber((s8)(C));
|
|
}
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : ForcePlaybackRecordedCarUpdate()
|
|
// PURPOSE : Forces a single update of the playback of a recording on the
|
|
// specified vehicle. This is equivalent to the update that would
|
|
// otherwise occur over the course of a *full* game update,
|
|
// irrespective of whether the recordings are normally updated
|
|
// within the physics timeslices.
|
|
// RETURNS :
|
|
// PARAMETERS :
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
void CVehicleRecordingMgr::ForcePlaybackRecordedCarUpdate(class CVehicle *pCar)
|
|
{
|
|
s32 C;
|
|
|
|
C = 0;
|
|
while (C < MAX_CARS_PLAYBACK_NUMBER && ( (!bPlaybackGoingOn[C]) || pVehicleForPlayback[C] != pCar))
|
|
{
|
|
C++;
|
|
}
|
|
|
|
if(Verifyf(C < MAX_CARS_PLAYBACK_NUMBER, "FORCE_PLAYBACK_RECORDED_VEHICLE_UPDATE is refering to a recording that does not exist"))
|
|
{
|
|
//NOTE: We use the current game time step (in milliseconds), as we are forcing a single update of the playback on this game update.
|
|
float TimeStep = (float)(fwTimer::GetTimeInMilliseconds() - fwTimer::GetPrevElapsedTimeInMilliseconds());
|
|
UpdatePlaybackForVehicleRecording(C, TimeStep);
|
|
}
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : DisplayPlaybackRecordedCar()
|
|
// PURPOSE :
|
|
// RETURNS :
|
|
// PARAMETERS :
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
#if !__FINAL
|
|
void CVehicleRecordingMgr::DisplayPlaybackRecordedCar(class CVehicle *pCar, s32 NewDisplayMode)
|
|
{
|
|
Assert(pCar);
|
|
|
|
for (s32 C = 0; C < MAX_CARS_PLAYBACK_NUMBER; C++)
|
|
{
|
|
if (bPlaybackGoingOn[C] && pCar == pVehicleForPlayback[C])
|
|
{
|
|
DisplayMode[C] = (u8)(NewDisplayMode);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
bool CVehicleRecordingMgr::Place(s32 index, datResourceMap& map, datResourceInfo& header)
|
|
{
|
|
CVehicleRecording * pR = NULL;
|
|
pgRscBuilder::PlaceStream(pR, header, map, "<unknown>");
|
|
|
|
sm_StreamingArray[index].Place(pR);
|
|
|
|
return true;
|
|
}
|
|
|
|
void CVehicleRecordingMgr::AddRef(strLocalIndex index)
|
|
{
|
|
sm_StreamingArray[index.Get()].AddRef();
|
|
}
|
|
|
|
void CVehicleRecordingMgr::RemoveRef(strLocalIndex index)
|
|
{
|
|
sm_StreamingArray[index.Get()].RemoveRef();
|
|
}
|
|
|
|
int CVehicleRecordingMgr::GetNumRefs(strLocalIndex index)
|
|
{
|
|
return sm_StreamingArray[index.Get()].GetNumRefs();
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : SetRecordingToPointClosestToCoors()
|
|
// PURPOSE : Goes through the recording and sets it to the point closest to the coordinates.
|
|
// RETURNS :
|
|
// PARAMETERS :
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
void CVehicleRecordingMgr::SetRecordingToPointClosestToCoors(s32 C, const Vector3& Coors, Vector3 *CoorsClosest)
|
|
{
|
|
s32 IndexInRecording = 0;
|
|
|
|
float NearestDist = 999999.9f;
|
|
while (IndexInRecording < PlaybackBufferSize[C])
|
|
{
|
|
CVehicleStateEachFrame *pVehState = (CVehicleStateEachFrame *) &((pPlaybackBuffer[C])[IndexInRecording]);
|
|
|
|
float Distance = (Coors - Vector3(pVehState->CoorsX, pVehState->CoorsY, pVehState->CoorsZ) ).Mag();
|
|
if (Distance < NearestDist)
|
|
{
|
|
PlaybackIndex[C] = IndexInRecording;
|
|
NearestDist = Distance;
|
|
if(CoorsClosest)
|
|
{
|
|
CoorsClosest->Set(pVehState->CoorsX, pVehState->CoorsY, pVehState->CoorsZ);
|
|
}
|
|
}
|
|
|
|
IndexInRecording += sizeof(CVehicleStateEachFrame);
|
|
}
|
|
}
|
|
|
|
// returns the slot in the playback arrays
|
|
int CVehicleRecordingMgr::GetPlaybackSlot(const CVehicle* pCar)
|
|
{
|
|
int C = 0;
|
|
|
|
while (C < MAX_CARS_PLAYBACK_NUMBER)
|
|
{
|
|
if(bPlaybackGoingOn[C] && pVehicleForPlayback[C] == pCar)
|
|
return C;
|
|
C++;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int CVehicleRecordingMgr::GetPlaybackSlotFast(const CVehicle* pCar)
|
|
{
|
|
if(pCar->GetIntelligence())
|
|
{
|
|
return pCar->GetIntelligence()->GetRecordingNumber();
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : StopPlaybackRecordedCar()
|
|
// PURPOSE : Stops playing back the recorded data for this car.
|
|
// RETURNS :
|
|
// PARAMETERS :
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
void CVehicleRecordingMgr::StopPlaybackRecordedCar(class CVehicle *pCar)
|
|
{
|
|
int slot = GetPlaybackSlot(pCar);
|
|
if(slot != -1)
|
|
StopPlaybackWithIndex(slot);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : StopPlaybackRecordedCar()
|
|
// PURPOSE : Stops playing back the recorded data for this car.
|
|
// RETURNS :
|
|
// PARAMETERS :
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void CVehicleRecordingMgr::StopPlaybackWithIndex(s32 C)
|
|
{
|
|
Assert(C<MAX_CARS_PLAYBACK_NUMBER);
|
|
|
|
// Assert(pVehicleForPlayback[C]); This is not always the case since we can call StopPlaybackWithIndex after the vehicle is removed.
|
|
if (pVehicleForPlayback[C])
|
|
{
|
|
pVehicleForPlayback[C]->GetIntelligence()->SetRecordingNumber(-1);
|
|
pVehicleForPlayback[C]->SelectAppropriateGearForSpeed();
|
|
|
|
if(pVehicleForPlayback[C] && pVehicleForPlayback[C]->GetCurrentPhysicsInst())
|
|
{
|
|
if(pVehicleForPlayback[C]->GetVehicleType() == VEHICLE_TYPE_TRAILER)
|
|
{
|
|
// Reatttach trailers that have been left attached whilst doing a car recording
|
|
CVehicle *pCar = pVehicleForPlayback[C];
|
|
CTrailer *pTrailer = static_cast<CTrailer*>(pCar);
|
|
CVehicle *pParent = (CVehicle*)pTrailer->GetAttachParent();
|
|
if(pParent)
|
|
{
|
|
pTrailer->DetachFromParent(0);
|
|
pTrailer->AttachToParentVehicle(pParent, false);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
pVehicleForPlayback[C]->m_vehControls.Reset();
|
|
pVehicleForPlayback[C]->m_vehControls.SetHandBrake(false);
|
|
|
|
// Force the default driving task to be reconstructed
|
|
CPed* pDriver = pVehicleForPlayback[C]->GetDriver();
|
|
if( pDriver )
|
|
{
|
|
// If the driver is exiting, don't bother recomputing default tasks as they're leaving
|
|
if (!pDriver->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_EXIT_VEHICLE)
|
|
&& !pDriver->IsLocalPlayer())
|
|
{
|
|
pDriver->GetPedIntelligence()->AddTaskDefault(pDriver->ComputeDefaultTask(*pDriver));
|
|
}
|
|
}
|
|
|
|
CTaskVehicleMissionBase *pCarTask = pVehicleForPlayback[C]->GetIntelligence()->GetActiveTask();
|
|
if(pCarTask)
|
|
{
|
|
pCarTask->RequestSoftReset();
|
|
}
|
|
|
|
if(pVehicleForPlayback[C]->GetCurrentPhysicsInst() && CPhysics::GetLevel()->IsInactive(pVehicleForPlayback[C]->GetCurrentPhysicsInst()->GetLevelIndex()))
|
|
{
|
|
pVehicleForPlayback[C]->ActivatePhysics();
|
|
}
|
|
|
|
pVehicleForPlayback[C]->m_nVehicleFlags.bLerpToFullRecording = false;
|
|
}
|
|
|
|
SetVehicleForPlayback(C, NULL);
|
|
// delete [] pPlaybackBuffer[C];
|
|
pPlaybackBuffer[C] = NULL;
|
|
PlaybackBufferSize[C] = 0;
|
|
bPlaybackGoingOn[C] = false;
|
|
strLocalIndex StreamingIndex = strLocalIndex(PlayBackStreamingIndex[C]);
|
|
if (StreamingIndex.Get() >= 0)
|
|
{
|
|
CVehicleRecordingMgr::GetStreamingModule()->RemoveRef(StreamingIndex, REF_OTHER);
|
|
}
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : IsPlaybackGoingOnForCar()
|
|
// PURPOSE : returns true if this car is playing recorded data.
|
|
// RETURNS :
|
|
// PARAMETERS :
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
bool CVehicleRecordingMgr::IsPlaybackGoingOnForCar(const CVehicle *pCar)
|
|
{
|
|
return pCar->IsRunningCarRecording();
|
|
}
|
|
|
|
int CVehicleRecordingMgr::GetPlaybackIdForCar(const CVehicle* pCar)
|
|
{
|
|
int slot = GetPlaybackSlotFast(pCar);
|
|
if(slot != -1)
|
|
return PlayBackStreamingIndex[slot];
|
|
return -1;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : IsPlaybackPausedForCar()
|
|
// PURPOSE : returns true if this car is paused.
|
|
// RETURNS :
|
|
// PARAMETERS :
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
bool CVehicleRecordingMgr::IsPlaybackPausedForCar(class CVehicle *pCar)
|
|
{
|
|
int slot = GetPlaybackSlotFast(pCar);
|
|
if (slot != -1)
|
|
return bPlaybackPaused[slot];
|
|
return false;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : IsPlaybackSwitchedToAiForCar()
|
|
// PURPOSE : returns true if this car is switched to AI.
|
|
// RETURNS :
|
|
// PARAMETERS :
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
bool CVehicleRecordingMgr::IsPlaybackSwitchedToAiForCar(const CVehicle *pCar)
|
|
{
|
|
int slot = GetPlaybackSlotFast(pCar);
|
|
if (slot != -1)
|
|
return bUseCarAI[slot];
|
|
return false;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : SetPlaybackSpeed()
|
|
// PURPOSE : Sets the new playback speed (1.0f = standard, 0.8f = 20% slower etc)
|
|
// RETURNS :
|
|
// PARAMETERS :
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
void CVehicleRecordingMgr::SetPlaybackSpeed(class CVehicle *pCar, float Speed)
|
|
{
|
|
int slot = GetPlaybackSlotFast(pCar);
|
|
if (slot != -1)
|
|
PlaybackSpeed[slot] = Speed;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : PausePlaybackRecordedCar()
|
|
// PURPOSE : Pauses playing back the recorded data for this car.
|
|
// RETURNS :
|
|
// PARAMETERS :
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
void CVehicleRecordingMgr::PausePlaybackRecordedCar(class CVehicle *pCar)
|
|
{
|
|
int slot = GetPlaybackSlotFast(pCar);
|
|
if (slot != -1)
|
|
bPlaybackPaused[slot] = true;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : UnpausePlaybackRecordedCar()
|
|
// PURPOSE : Pauses playing back the recorded data for this car.
|
|
// RETURNS :
|
|
// PARAMETERS :
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
void CVehicleRecordingMgr::UnpausePlaybackRecordedCar(class CVehicle *pCar)
|
|
{
|
|
int slot = GetPlaybackSlotFast(pCar);
|
|
if (slot != -1)
|
|
bPlaybackPaused[slot] = false;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : ChangeCarPlaybackToUseAI()
|
|
// PURPOSE : If a direct playback is going on but we'd like to use an AI one instead
|
|
// this function is called.
|
|
// RETURNS :
|
|
// PARAMETERS :
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
void CVehicleRecordingMgr::ChangeCarPlaybackToUseAI(class CVehicle *pVeh, u32 timeAfterWhichToTryAndReturnToFullRecording, s32 iDrivingFlags, bool bSnapToPositionIfNotVisible)
|
|
{
|
|
int slot = GetPlaybackSlot(pVeh);
|
|
if(!Verifyf(slot != -1, "CHANGE_CAR_PLAYBACK_TO_USE_AI looks like this car isn't playing back a recording at the moment"))
|
|
return;
|
|
|
|
CTrailer *pTrailerToReattach = NULL;
|
|
if(pVeh->GetAttachedTrailer() && IsPlaybackGoingOnForCar(pVeh->GetAttachedTrailer()) && !IsPlaybackSwitchedToAiForCar(pVeh->GetAttachedTrailer()))
|
|
{
|
|
pTrailerToReattach = pVeh->GetAttachedTrailer();
|
|
CVehicleRecordingMgr::ChangeCarPlaybackToUseAI(pTrailerToReattach, timeAfterWhichToTryAndReturnToFullRecording, iDrivingFlags, bSnapToPositionIfNotVisible);
|
|
}
|
|
|
|
bUseCarAI[slot] = true;
|
|
|
|
TurnBackIntoFullPlayBackTime[slot] = ~(u32)0;
|
|
if (timeAfterWhichToTryAndReturnToFullRecording)
|
|
{
|
|
TurnBackIntoFullPlayBackTime[slot] = fwTimer::GetTimeInMilliseconds() + timeAfterWhichToTryAndReturnToFullRecording;
|
|
}
|
|
|
|
CPed *pDriver = pVeh->GetDriver();
|
|
|
|
if (pDriver)
|
|
{
|
|
CTaskControlVehicle* pTask = NULL;
|
|
|
|
// Tell this car to follow this path. The AI will take over from here,
|
|
sVehicleMissionParams params;
|
|
params.m_fCruiseSpeed = 20.0f;
|
|
params.m_iDrivingFlags = iDrivingFlags;
|
|
|
|
aiTask *pCarTask = rage_new CTaskVehicleFollowRecording(params);
|
|
pTask = rage_new CTaskControlVehicle(pVeh, pCarTask);
|
|
|
|
//if we were in a driveby, make sure we restore it
|
|
CTaskVehicleGun* pTaskVehicleGun = static_cast<CTaskVehicleGun*>(pDriver->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_VEHICLE_GUN));
|
|
CTaskVehicleGun* pNewTaskVehicleGun = NULL;
|
|
if (pTaskVehicleGun)
|
|
{
|
|
pNewTaskVehicleGun = static_cast<CTaskVehicleGun*>(pTaskVehicleGun->Copy());
|
|
}
|
|
|
|
pDriver->GetPedIntelligence()->AddTaskAtPriority(pTask, PED_TASK_PRIORITY_PRIMARY);
|
|
if (pNewTaskVehicleGun)
|
|
pTask->SetDesiredSubtask(pNewTaskVehicleGun);
|
|
|
|
Vector3 vClosestCoords = Vector3(Vector3::ZeroType);
|
|
SetRecordingToPointClosestToCoors(slot, VEC3V_TO_VECTOR3(pVeh->GetVehiclePosition()), &vClosestCoords);
|
|
|
|
if(bSnapToPositionIfNotVisible && !pVeh->GetIsVisibleInSomeViewportThisFrame() && vClosestCoords.IsNonZero())
|
|
{
|
|
pVeh->SetPosition(vClosestCoords, true, true, true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pVeh->m_vehControls.Reset();
|
|
}
|
|
|
|
if(pVeh->GetCurrentPhysicsInst() && CPhysics::GetLevel()->IsInactive(pVeh->GetCurrentPhysicsInst()->GetLevelIndex()))
|
|
{
|
|
pVeh->ActivatePhysics();
|
|
}
|
|
|
|
if(pTrailerToReattach)
|
|
{
|
|
//Force re-attach to ensure constraints are set
|
|
pTrailerToReattach->AttachToParentVehicle(pVeh, true, 1.0f, true);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : SkipToEndOfFile
|
|
// PURPOSE : This will jump to the last recorded state.
|
|
// The specified car will be set to this state.
|
|
// (This should make it easier to record sequences of recodings)
|
|
// RETURNS :
|
|
// PARAMETERS :
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
void CVehicleRecordingMgr::SkipToEndAndStopPlaybackRecordedCar(class CVehicle *pCar)
|
|
{
|
|
int slot = GetPlaybackSlot(pCar);
|
|
|
|
if (Verifyf(slot != -1, "SkipToEndAndStopPlaybackRecordedCar: looks like this car isn't playing back a recording at the moment"))
|
|
{
|
|
// Jump to the last stored state in the file.
|
|
//pVehicleForPlayback[C]->m_nPhysicalFlags.bInfiniteMass = false;
|
|
Matrix34 PreviousMatrix = MAT34V_TO_MATRIX34(pVehicleForPlayback[slot]->GetMatrix());
|
|
CVehicleStateEachFrame *pLastVehState = (CVehicleStateEachFrame *) &((pPlaybackBuffer[slot])[PlaybackBufferSize[slot]]);
|
|
pLastVehState--;
|
|
|
|
float playbackSpeed;
|
|
if(bPlaybackPaused[slot])
|
|
{
|
|
playbackSpeed = 0.0f;
|
|
}
|
|
#if __DEV
|
|
// If this is the debug recording as triggered by the widgets; allow user to change the playback speed with the slider.
|
|
else if(debug_WidgetRecordingPlaybackVehicle == pVehicleForPlayback[slot] && pPlaybackBuffer[slot] == debug_pDebugBuffer)
|
|
{
|
|
playbackSpeed = debug_timeScale;
|
|
}
|
|
#endif
|
|
else
|
|
{
|
|
playbackSpeed = PlaybackSpeed[slot];
|
|
}
|
|
|
|
RestoreInfoForCar(pVehicleForPlayback[slot], pLastVehState, false, playbackSpeed, true);
|
|
|
|
// Code needed to stop the wheels applying silly forces.
|
|
for (s32 i = 0; i < pVehicleForPlayback[slot]->GetNumWheels(); i++)
|
|
{
|
|
pVehicleForPlayback[slot]->GetWheel(i)->UpdateContactsAfterNetworkBlend(PreviousMatrix, MAT34V_TO_MATRIX34(pVehicleForPlayback[slot]->GetMatrix()));
|
|
}
|
|
|
|
StopPlaybackWithIndex(slot);
|
|
|
|
pCar->GetIntelligence()->SetRecordingNumber(-1);
|
|
|
|
pCar->SetAngVelocity(Vector3(0.0f,0.0f,0.0f));
|
|
pCar->SetVelocity(Vector3(0.0f,0.0f,0.0f));
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : SaveDataForThisFrame()
|
|
// PURPOSE : All the info that has to be saved for this frame is.
|
|
// This includes all the possible input as well as the
|
|
// time elapsed.
|
|
// RETURNS :
|
|
// PARAMETERS :
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
//u8 BufferDataToBeSaved[128];
|
|
// should be in game.cpp right after CClock::Update();
|
|
|
|
#if !__FINAL
|
|
void CVehicleRecordingMgr::SaveDataForThisFrame()
|
|
{
|
|
#if GTA_REPLAY
|
|
if(CReplayMgr::IsEditModeActive())
|
|
return;
|
|
#endif
|
|
|
|
if (CutSceneManager::GetInstance() && CutSceneManager::GetInstance()->IsPlaying())
|
|
return;
|
|
|
|
// Do what needs to be done if we're recording a cars position.
|
|
for (s32 Recording = 0; Recording < MAX_CARS_RECORDED_NUMBER; Recording++)
|
|
{
|
|
if (ms_vehiclesRecordings[Recording].IsRecording())
|
|
{
|
|
ms_vehiclesRecordings[Recording].SaveFrame();
|
|
}
|
|
}
|
|
|
|
#if __BANK
|
|
// The user can request for a certain recording to be reduced.
|
|
// This can be used for things like the buses.
|
|
// if (bReduceRecording)
|
|
// {
|
|
// ReduceRecording(RecordingToBeReduced, ReductionSampleStep);
|
|
// bReduceRecording = false;
|
|
// }
|
|
if (bShiftRecording)
|
|
{
|
|
CRecording::ShiftRecording(RecordingNumberToBeShifted, RecordingNameToBeShifted, ShiftX, ShiftY, ShiftZ);
|
|
bShiftRecording = false;
|
|
}
|
|
if (bFixRecordingTimeStamps)
|
|
{
|
|
CRecording::FixRecordingTimeStamps(RecordingNumberToBeShifted, RecordingNameToBeShifted);
|
|
bFixRecordingTimeStamps = false;
|
|
}
|
|
#endif // __BANK
|
|
}
|
|
#endif // !__FINAL
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : RetrieveDataForThisFrame()
|
|
// PURPOSE : All the info that has to be played back for this frame is.
|
|
// RETURNS :
|
|
// PARAMETERS :
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
void CVehicleRecordingMgr::RetrieveDataForThisFrame()
|
|
{
|
|
#if GTA_REPLAY
|
|
if(CReplayMgr::IsEditModeActive())
|
|
return;
|
|
#endif
|
|
//Allow car recordings for cut scenes, enabled for mag demo not sure why we dont want it full stop
|
|
/*if(CutSceneManager::GetInstance() && CutSceneManager::GetInstance()->IsPlaying())
|
|
return;*/
|
|
|
|
float TimeStep = (float)(fwTimer::GetTimeInMilliseconds() - fwTimer::GetPrevElapsedTimeInMilliseconds());
|
|
|
|
if(CVehicleRecordingMgr::sm_bUpdateWithPhysics)
|
|
{
|
|
// NOTE: We must still respect the game timer when updating within the physics time slices, as the game timer
|
|
// accumulates fixed-point time steps, in milliseconds, and script must be able to control recordings using the
|
|
// game timer. If we accumulated time correctly here, using the floating-point physics time step, we'd accumulate
|
|
// error w.r.t. the game timer.
|
|
|
|
int nNumTimeSlices = CPhysics::GetNumTimeSlices();
|
|
if(nNumTimeSlices > 0)
|
|
{
|
|
TimeStep /= (float)nNumTimeSlices;
|
|
}
|
|
}
|
|
|
|
// Update the recordings that don't necessarily have vehicles with them (buses etc)
|
|
|
|
// Do what needs to be done to handle playback of cars.
|
|
for (s32 C = 0; C < MAX_CARS_PLAYBACK_NUMBER; C++)
|
|
{
|
|
UpdatePlaybackForVehicleRecording(C, TimeStep);
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : UpdatePlaybackForVehicle()
|
|
// PURPOSE : Handles the playback of a specific vehicle recording.
|
|
// RETURNS :
|
|
// PARAMETERS :
|
|
///////////////////////////////////////////////////////////////////////////
|
|
float fMinSpeedForAccelerationInterpolation = 5.0f;
|
|
void CVehicleRecordingMgr::UpdatePlaybackForVehicleRecording(s32 RecordingIndex, float TimeStep)
|
|
{
|
|
if (bPlaybackGoingOn[RecordingIndex] && !(RecordingIndex < FOR_CODE_INDEX_LAST && pVehicleForPlayback[RecordingIndex]) ) // Don't update code recordings with cars. The ai should update those.
|
|
{
|
|
const bool bContinueRecordingIfCarDestroyed = (GetVehicleRecordingFlags(RecordingIndex) & VRF_ContinueRecordingIfCarDestroyed) != 0;
|
|
if ( (((RecordingIndex >= FOR_CODE_INDEX_LAST) && (!pVehicleForPlayback[RecordingIndex])) || (!bContinueRecordingIfCarDestroyed && pVehicleForPlayback[RecordingIndex]->m_nPhysicalFlags.bRenderScorched))) // We're done with this playback
|
|
{
|
|
StopPlaybackWithIndex(RecordingIndex);
|
|
}
|
|
else if (bUseCarAI[RecordingIndex])
|
|
{
|
|
if (TurnBackIntoFullPlayBackTime[RecordingIndex])
|
|
{
|
|
TryToTurnAIRecordingBackIntoFull(RecordingIndex);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
float playbackSpeed;
|
|
if(bPlaybackPaused[RecordingIndex])
|
|
{
|
|
playbackSpeed = 0.0f;
|
|
}
|
|
#if __DEV
|
|
// If this is the debug recording as triggered by the widgets; allow user to change the playback speed with the slider.
|
|
else if(debug_WidgetRecordingPlaybackVehicle == pVehicleForPlayback[RecordingIndex] && pPlaybackBuffer[RecordingIndex] == debug_pDebugBuffer)
|
|
{
|
|
playbackSpeed = debug_timeScale;
|
|
}
|
|
#endif
|
|
else
|
|
{
|
|
playbackSpeed = PlaybackSpeed[RecordingIndex];
|
|
}
|
|
|
|
if (!bPlaybackPaused[RecordingIndex])
|
|
{
|
|
if (RecordingIndex < FOR_CODE_INDEX_LAST) // This is a recording used by code (ie bus)
|
|
{
|
|
PlaybackRunningTime[RecordingIndex] = (float)FindPlaybackRunningTimeAccordingToSchedule(RecordingIndex, 0);
|
|
}
|
|
else
|
|
{
|
|
// The following bit used to smoothe out the playback by averaging the timestep over a few frames
|
|
// We might want to reinstate this at some point. (Better yet; improve the recordings)
|
|
|
|
float TimeStepScaled = TimeStep * playbackSpeed;
|
|
|
|
const float maxTimeStep = 250.0f;
|
|
if (TimeStepScaled > maxTimeStep)
|
|
{
|
|
Warningf("The vehicle recording time step (%fms) is being limited to %fms", TimeStepScaled, maxTimeStep);
|
|
|
|
TimeStepScaled = maxTimeStep;
|
|
}
|
|
|
|
PlaybackRunningTime[RecordingIndex] += TimeStepScaled;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
u32 EndTimeInRecording = ((CVehicleStateEachFrame *) &((pPlaybackBuffer[RecordingIndex])[PlaybackBufferSize[RecordingIndex]-sizeof(CVehicleStateEachFrame)]))->TimeInRecording;
|
|
|
|
// Deal with time getting below 0
|
|
if (PlaybackRunningTime[RecordingIndex] < 0.0f)
|
|
{
|
|
if (bPlaybackLooped[RecordingIndex])
|
|
{
|
|
PlaybackRunningTime[RecordingIndex] = (float)EndTimeInRecording;
|
|
}
|
|
else
|
|
{
|
|
PlaybackRunningTime[RecordingIndex] = 0.0f;
|
|
}
|
|
}
|
|
|
|
// Deal with running off the end of the recording.
|
|
if (PlaybackRunningTime[RecordingIndex] >= EndTimeInRecording)
|
|
{
|
|
if (bPlaybackLooped[RecordingIndex])
|
|
{
|
|
PlaybackRunningTime[RecordingIndex] = 0.0f;
|
|
PlaybackIndex[RecordingIndex] = 0;
|
|
}
|
|
else
|
|
{
|
|
PlaybackRunningTime[RecordingIndex] = (float)EndTimeInRecording;
|
|
StopPlaybackRecordedCar(pVehicleForPlayback[RecordingIndex]);
|
|
}
|
|
}
|
|
|
|
|
|
if (pVehicleForPlayback[RecordingIndex]) // Make sure recording hasn't been stopped by now.
|
|
{
|
|
|
|
CVehicleStateEachFrame *pVehState = (CVehicleStateEachFrame *) &((pPlaybackBuffer[RecordingIndex])[PlaybackIndex[RecordingIndex]]);
|
|
CVehicleStateEachFrame *pVehNextState = pVehState+1;
|
|
CVehicleStateEachFrame *pVehNextOfNextState = pVehState+2;
|
|
CVehicleStateEachFrame *pVehPreviousState = pVehState-1;
|
|
|
|
while (PlaybackRunningTime[RecordingIndex] > pVehNextState->TimeInRecording && pVehNextState < (CVehicleStateEachFrame *) &((pPlaybackBuffer[RecordingIndex])[PlaybackBufferSize[RecordingIndex]]))
|
|
{
|
|
PlaybackIndex[RecordingIndex] += sizeof(CVehicleStateEachFrame);
|
|
pVehPreviousState++;
|
|
pVehState++;
|
|
pVehNextState++;
|
|
pVehNextOfNextState++;
|
|
}
|
|
|
|
// The PlaybackSpeed can be negative in which case we move backwards through the playback.
|
|
while (PlaybackRunningTime[RecordingIndex] < pVehState->TimeInRecording && pVehState > (CVehicleStateEachFrame *) &((pPlaybackBuffer[RecordingIndex])[0]))
|
|
{
|
|
PlaybackIndex[RecordingIndex] -= sizeof(CVehicleStateEachFrame);
|
|
pVehPreviousState--;
|
|
pVehState--;
|
|
pVehNextState--;
|
|
pVehNextOfNextState--;
|
|
}
|
|
|
|
if(pVehNextState == (CVehicleStateEachFrame *) &((pPlaybackBuffer[RecordingIndex])[PlaybackBufferSize[RecordingIndex]]))
|
|
{
|
|
pVehNextOfNextState = NULL;
|
|
}
|
|
|
|
if(pVehState == (CVehicleStateEachFrame *) &((pPlaybackBuffer[RecordingIndex])[0]))
|
|
{
|
|
pVehPreviousState = NULL;
|
|
}
|
|
|
|
Matrix34 PreviousMatrix = MAT34V_TO_MATRIX34(pVehicleForPlayback[RecordingIndex]->GetMatrix());
|
|
|
|
// Store the previous matrix as it is used when updating the possiblyTouchesWaterFlag, etc.
|
|
pVehicleForPlayback[RecordingIndex]->SetPreviousPosition(PreviousMatrix.d);
|
|
|
|
RestoreInfoForCar(pVehicleForPlayback[RecordingIndex], pVehState, false, playbackSpeed, (aiVechicleRecordingFlags[RecordingIndex] & VRF_FirstUpdate) > 0);
|
|
|
|
// If this is the very first frame of the recording, using the initial playback matrix as the previous matrix
|
|
if(aiVechicleRecordingFlags[RecordingIndex] & VRF_FirstUpdate)
|
|
{
|
|
pVehicleForPlayback[RecordingIndex].Get()->m_Transmission.SelectAppropriateGearForSpeed();
|
|
PreviousMatrix = MAT34V_TO_MATRIX34(pVehicleForPlayback[RecordingIndex]->GetMatrix());
|
|
pVehicleForPlayback[RecordingIndex]->SetPreviousPosition(PreviousMatrix.d);
|
|
aiVechicleRecordingFlags[RecordingIndex] &= ~VRF_FirstUpdate;
|
|
}
|
|
|
|
|
|
float Interpolation = float(PlaybackRunningTime[RecordingIndex]-pVehState->TimeInRecording) / (pVehNextState->TimeInRecording - pVehState->TimeInRecording);
|
|
|
|
// Smooth out the velocity transition from record to record when playing in slow motion
|
|
if(fwTimer::GetTimeWarpActive()<1.f && pVehNextOfNextState && pVehPreviousState)
|
|
{
|
|
Vector3 vPreviousRecordPos = pVehPreviousState->GetCoors();
|
|
Vector3 vCurrentRecordPos = pVehState->GetCoors();
|
|
Vector3 vNextRecordPos = pVehNextState->GetCoors();
|
|
Vector3 vNextOfNextRecordPos = pVehNextOfNextState->GetCoors();
|
|
|
|
float fCurrentRecordDistance = (vNextRecordPos - vCurrentRecordPos).Mag();
|
|
|
|
float fAveragePreviousRecordSpeed = (vCurrentRecordPos - vPreviousRecordPos).Mag() / (float(pVehState->TimeInRecording - pVehPreviousState->TimeInRecording) * 0.001f);
|
|
float fAverageCurrentRecordSpeed = fCurrentRecordDistance / (float(pVehNextState->TimeInRecording - pVehState->TimeInRecording) * 0.001f);
|
|
float fAverageNextRecordSpeed = (vNextOfNextRecordPos - vNextRecordPos).Mag() / (float(pVehNextOfNextState->TimeInRecording - pVehNextState->TimeInRecording) * 0.001f);
|
|
|
|
if(fAveragePreviousRecordSpeed > fMinSpeedForAccelerationInterpolation && fAverageCurrentRecordSpeed > fMinSpeedForAccelerationInterpolation && fAverageNextRecordSpeed > fMinSpeedForAccelerationInterpolation)
|
|
{
|
|
float fAccelerationPreviousToCurrent = (fAverageCurrentRecordSpeed - fAveragePreviousRecordSpeed) / (float(pVehState->TimeInRecording - pVehPreviousState->TimeInRecording) * 0.001f);
|
|
float fAccelerationCurrentToNext = (fAverageNextRecordSpeed - fAverageCurrentRecordSpeed) / (float(pVehNextState->TimeInRecording - pVehState->TimeInRecording) * 0.001f);
|
|
float fAcceleration = (fAccelerationPreviousToCurrent + fAccelerationCurrentToNext) * 0.5f;
|
|
float fTimePassed = float(PlaybackRunningTime[RecordingIndex] - pVehState->TimeInRecording) * 0.001f;
|
|
|
|
float fStartSpeed = fAverageCurrentRecordSpeed - fAcceleration * (float(pVehNextState->TimeInRecording - pVehState->TimeInRecording) * 0.001f) * 0.5f;
|
|
|
|
// P1 = P0 + V0t + 1/2aT^2
|
|
float fDistanceTraveled = fStartSpeed * fTimePassed + fAcceleration * (fTimePassed * fTimePassed * 0.5f);
|
|
Interpolation = fDistanceTraveled / fCurrentRecordDistance;
|
|
Interpolation = Clamp(Interpolation, 0.0f, 1.0f);
|
|
}
|
|
}
|
|
|
|
Assert(Interpolation >= 0.0f && Interpolation <= 1.0f);
|
|
|
|
Quaternion additionalRotation;
|
|
int timeSlice = CPhysics::GetCurrentTimeSlice();
|
|
#if __BANK
|
|
// If this is the debug recording as triggered by the widgets; allow user to override the additional rotation to be applied.
|
|
if((debug_AdditionalRotationEulers.Mag2() > VERY_SMALL_FLOAT) &&
|
|
(debug_WidgetRecordingPlaybackVehicle == pVehicleForPlayback[RecordingIndex]) && (pPlaybackBuffer[RecordingIndex] == debug_pDebugBuffer))
|
|
{
|
|
additionalRotation.FromEulers(debug_AdditionalRotationEulers * DtoR, eEulerOrderYXZ);
|
|
}
|
|
else
|
|
#endif // __BANK
|
|
{
|
|
Vec3V additionalRotationAxisAngle;
|
|
if(timeSlice != -1 && !CPhysics::GetIsLastTimeSlice(timeSlice))
|
|
{
|
|
// If we're the middle of the frame, interpolate the rotation
|
|
ScalarV frameInterpolation = ScalarVFromF32((float)(timeSlice + 1)/CPhysics::GetNumTimeSlices());
|
|
additionalRotationAxisAngle = Lerp(frameInterpolation,PlaybackPreviousAdditionalRotation[RecordingIndex],PlaybackAdditionalRotation[RecordingIndex]);
|
|
}
|
|
else
|
|
{
|
|
// If we reach the end of the frame, store off the current rotation as the previous
|
|
additionalRotationAxisAngle = PlaybackAdditionalRotation[RecordingIndex];
|
|
PlaybackPreviousAdditionalRotation[RecordingIndex] = PlaybackAdditionalRotation[RecordingIndex];
|
|
}
|
|
ScalarV rotationAngle = Mag(additionalRotationAxisAngle);
|
|
Vec3V rotationAxis = InvScaleSafe(additionalRotationAxisAngle,rotationAngle,Vec3V(V_ZERO));
|
|
additionalRotation = QUATV_TO_QUATERNION(QuatVFromAxisAngle(rotationAxis,rotationAngle));
|
|
}
|
|
|
|
Vec3V localPositionOffset = PlaybackLocalPositionOffset[RecordingIndex];
|
|
BANK_ONLY(localPositionOffset = Add(localPositionOffset,debug_LocalPositionOffset);)
|
|
|
|
Vec3V globalPositionOffset = PlaybackGlobalPositionOffset[RecordingIndex];
|
|
BANK_ONLY(globalPositionOffset = Add(globalPositionOffset,debug_GlobalPositionOffset);)
|
|
|
|
InterpolateInfoForCar(pVehicleForPlayback[RecordingIndex], pVehNextState, Interpolation, playbackSpeed, additionalRotation, localPositionOffset, globalPositionOffset, PreviousMatrix, TimeStep);
|
|
|
|
Matrix34 m = MAT34V_TO_MATRIX34(pVehicleForPlayback[RecordingIndex]->GetMatrix());
|
|
for (s32 i = 0; i < pVehicleForPlayback[RecordingIndex]->GetNumWheels(); i++)
|
|
{
|
|
pVehicleForPlayback[RecordingIndex]->GetWheel(i)->UpdateContactsAfterNetworkBlend(PreviousMatrix, m);
|
|
}
|
|
|
|
// If the vehicle has made a big jump we need to reset suspension or huge forces will be applied.
|
|
if ((VEC3V_TO_VECTOR3(pVehicleForPlayback[RecordingIndex]->GetVehiclePosition()) - PreviousMatrix.d).Mag() > 5.0f)
|
|
{
|
|
pVehicleForPlayback[RecordingIndex]->ResetSuspension();
|
|
|
|
CInteriorInst* pDestInteriorInst = 0;
|
|
s32 destRoomIdx = -1;
|
|
bool setWaitForAllCollisionsBeforeProbe = false;
|
|
Matrix34 mat = MAT34V_TO_MATRIX34(pVehicleForPlayback[RecordingIndex]->GetMatrix());
|
|
if(pVehicleForPlayback[RecordingIndex]->InheritsFromBike())
|
|
{
|
|
CBike::PlaceOnRoadProperly(static_cast<CBike*>(pVehicleForPlayback[RecordingIndex].Get()),&mat,pDestInteriorInst,destRoomIdx,setWaitForAllCollisionsBeforeProbe,
|
|
pVehicleForPlayback[RecordingIndex]->GetModelIndex(), pVehicleForPlayback[RecordingIndex]->GetCurrentPhysicsInst(),
|
|
false, NULL, NULL, 0, 1.0f, 1.0f, true);
|
|
}
|
|
else if(pVehicleForPlayback[RecordingIndex]->InheritsFromAutomobile())
|
|
{
|
|
CAutomobile::PlaceOnRoadProperly(static_cast<CAutomobile*>(pVehicleForPlayback[RecordingIndex].Get()),&mat,pDestInteriorInst,destRoomIdx,setWaitForAllCollisionsBeforeProbe,
|
|
pVehicleForPlayback[RecordingIndex]->GetModelIndex(), pVehicleForPlayback[RecordingIndex]->GetCurrentPhysicsInst(),
|
|
false, NULL, NULL, 0, PLACEONROAD_DEFAULTHEIGHTUP, PLACEONROAD_DEFAULTHEIGHTDOWN);
|
|
}
|
|
}
|
|
|
|
|
|
// If the vehicle in question is a train we need to set the PositionOnTrack
|
|
if (pVehicleForPlayback[RecordingIndex]->GetVehicleType() == VEHICLE_TYPE_TRAIN)
|
|
{
|
|
((CTrain *)pVehicleForPlayback[RecordingIndex].Get())->SetTrackPosFromWorldPos();
|
|
}
|
|
|
|
#if __BANK && __STATS
|
|
// If this is the debug recording as triggered by the widgets, graph the effective move speed.
|
|
if(debug_WidgetRecordingPlaybackVehicle == pVehicleForPlayback[RecordingIndex] && pPlaybackBuffer[RecordingIndex] == debug_pDebugBuffer)
|
|
{
|
|
const Vector3 vehiclePosition = VEC3V_TO_VECTOR3(pVehicleForPlayback[RecordingIndex]->GetVehiclePosition());
|
|
|
|
if(!g_VehiclePositionOnPreviousUpdate.IsClose(VEC3_MAX, SMALL_FLOAT))
|
|
{
|
|
#if __STATS
|
|
const float maxTimeStep = 250.0f;
|
|
const float TimeStepScaled = Min(TimeStep * playbackSpeed, maxTimeStep);
|
|
|
|
//NOTE: TimeStepScaled is in milliseconds...
|
|
const float effectiveSpeed = vehiclePosition.Dist(g_VehiclePositionOnPreviousUpdate) * 1000.0f / TimeStepScaled;
|
|
|
|
PF_SET(vehicleEffectiveSpeed, effectiveSpeed);
|
|
|
|
const Vector3& velocity = pVehicleForPlayback[RecordingIndex]->GetVelocity();
|
|
const float actualSpeed = velocity.Mag();
|
|
|
|
PF_SET(vehicleActualSpeed, actualSpeed);
|
|
#endif // __STATS
|
|
}
|
|
|
|
g_VehiclePositionOnPreviousUpdate = vehiclePosition;
|
|
}
|
|
#endif // __BANK
|
|
|
|
#if __ASSERT
|
|
Assert(rage::Abs(pVehicleForPlayback[RecordingIndex]->GetVelocity().x) < 200.0f);
|
|
Assert(rage::Abs(pVehicleForPlayback[RecordingIndex]->GetVelocity().y) < 200.0f);
|
|
Assert(rage::Abs(pVehicleForPlayback[RecordingIndex]->GetVelocity().z) < 200.0f);
|
|
|
|
const Vector3 vVehiclePosition = VEC3V_TO_VECTOR3(pVehicleForPlayback[RecordingIndex]->GetVehiclePosition());
|
|
Assert(rage::Abs(rage::Abs(vVehiclePosition.x)) < 10000.0f);
|
|
Assert(rage::Abs(rage::Abs(vVehiclePosition.y)) < 10000.0f);
|
|
Assert(rage::Abs(rage::Abs(vVehiclePosition.z)) < 10000.0f);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : RestoreInfoForMatrix()
|
|
// PURPOSE : Uncompresses the matrix to be played back in an animation.
|
|
///////////////////////////////////////////////////////////////////////////
|
|
void CVehicleRecordingMgr::RestoreInfoForMatrix(Matrix34& mat, class CVehicleStateEachFrame *pCarState)
|
|
{
|
|
Vector3 ThirdVec;
|
|
|
|
static const float s_fNormalizer = 1.0f / 127.0f;
|
|
mat.a.x = pCarState->Matrix_a_x * s_fNormalizer;
|
|
mat.a.y = pCarState->Matrix_a_y * s_fNormalizer;
|
|
mat.a.z = pCarState->Matrix_a_z * s_fNormalizer;
|
|
mat.b.x = pCarState->Matrix_b_x * s_fNormalizer;
|
|
mat.b.y = pCarState->Matrix_b_y * s_fNormalizer;
|
|
mat.b.z = pCarState->Matrix_b_z * s_fNormalizer;
|
|
|
|
// Work out the third vector of the matrix.
|
|
ThirdVec = CrossProduct(mat.a, mat.b);
|
|
mat.c.x = ThirdVec.x;
|
|
mat.c.y = ThirdVec.y;
|
|
mat.c.z = ThirdVec.z;
|
|
|
|
mat.Normalize(); // Rage needs the matrix to be orthonormal
|
|
Assert(mat.IsOrthonormal());
|
|
|
|
mat.d.x = pCarState->CoorsX;
|
|
mat.d.y = pCarState->CoorsY;
|
|
mat.d.z = pCarState->CoorsZ;
|
|
|
|
#if __BANK
|
|
mat.d.z += fShiftPlaybackUpValue;
|
|
#endif
|
|
|
|
}
|
|
|
|
void CVehicleRecordingMgr::RestoreInfoForPosition(Vector3& pos, class CVehicleStateEachFrame *pCarState)
|
|
{
|
|
pos.x = pCarState->CoorsX;
|
|
pos.y = pCarState->CoorsY;
|
|
pos.z = pCarState->CoorsZ;
|
|
|
|
#if __BANK
|
|
pos.z += fShiftPlaybackUpValue;
|
|
#endif
|
|
}
|
|
|
|
void CVehicleRecordingMgr::HandleRecordingTeleport(CVehicle *pCar, const Matrix34 &matOld, bool bUpdateTrailer)
|
|
{
|
|
pCar->UpdateGadgetsAfterTeleport(matOld, true, bUpdateTrailer);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : RestoreInfoForCar()
|
|
// PURPOSE : Uncompresses the matrix and stuff for a car to be played back
|
|
// in an animation.
|
|
///////////////////////////////////////////////////////////////////////////
|
|
static dev_float fMinDistSqDeltaToWarpBackToFull = square(0.01f);
|
|
static dev_float fMinHeadingDeltaToWarpBackToFull = SMALL_FLOAT;
|
|
|
|
void CVehicleRecordingMgr::RestoreInfoForCar(CVehicle *pCar, CVehicleStateEachFrame *pCarState, bool bAnimEnd, float playbackSpeed, bool bUpdateTrailer)
|
|
{
|
|
Matrix34 Mat;
|
|
|
|
RestoreInfoForMatrix(Mat, pCarState);
|
|
|
|
Mat34V oldMat = pCar->GetMatrix();
|
|
const Vec3V angVel = rage::SimdTransformUtil::CalculateAngularVelocity(MAT33V_ARG(oldMat.GetMat33()), MAT33V_ARG(MATRIX34_TO_MAT34V(Mat).GetMat33()));
|
|
pCar->SetAngVelocity(VEC3V_TO_VECTOR3(angVel));
|
|
|
|
const Matrix34 matOld = MAT34V_TO_MATRIX34(pCar->GetMatrix());
|
|
if(pCar->m_nVehicleFlags.bLerpToFullRecording)
|
|
{
|
|
float fHeadingDelta = camFrame::ComputeHeadingFromMatrix(Mat) - camFrame::ComputeHeadingFromMatrix(matOld);
|
|
pCar->m_nVehicleFlags.bLerpToFullRecording = matOld.d.Dist2(Mat.d) > fMinDistSqDeltaToWarpBackToFull || Abs(fHeadingDelta) > fMinHeadingDeltaToWarpBackToFull;
|
|
}
|
|
|
|
if(!pCar->m_nVehicleFlags.bLerpToFullRecording)
|
|
{
|
|
pCar->SetMatrix(Mat, true, true);
|
|
HandleRecordingTeleport(pCar, matOld, bUpdateTrailer);
|
|
}
|
|
|
|
pCar->m_vehControls.m_steerAngle = pCarState->SteerAngle / 20.0f;
|
|
pCar->m_vehControls.m_throttle = pCarState->Gas / 100.0f;
|
|
pCar->m_vehControls.m_brake = pCarState->Brake / 100.0f;
|
|
if (pCarState->HandBrake) pCar->m_vehControls.m_handBrake = true; else pCar->m_vehControls.m_handBrake = false;
|
|
|
|
if (bAnimEnd)
|
|
{
|
|
pCar->m_vehControls.m_throttle = 0.0f;
|
|
pCar->m_vehControls.m_brake = 0.0f;
|
|
pCar->SetVelocity(ORIGIN);
|
|
pCar->m_vehControls.m_handBrake = false;
|
|
}
|
|
else
|
|
{
|
|
Vector3 vecSpeed(pCarState->GetSpeedX() * playbackSpeed, pCarState->GetSpeedY() * playbackSpeed, pCarState->GetSpeedZ() * playbackSpeed);
|
|
|
|
//Ensure the magnitude does not exceed a certain threshold.
|
|
static ScalarV scMaxMagSq = ScalarVFromF32(rage::square(80.0f));
|
|
ScalarV scMagSq = MagSquared(VECTOR3_TO_VEC3V(vecSpeed));
|
|
if(IsLessThanAll(scMagSq, scMaxMagSq))
|
|
{
|
|
pCar->SetVelocity(vecSpeed);
|
|
}
|
|
}
|
|
|
|
// Recorded bikes are never on their side stand really
|
|
if (pCar->InheritsFromBike())
|
|
{
|
|
((CBike *)pCar)->m_nBikeFlags.bOnSideStand = false;
|
|
((CBike *)pCar)->m_nBikeFlags.bGettingPickedUp = false;
|
|
}
|
|
|
|
Assert(pCar->GetVehiclePosition().GetZf() > -500.0f);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : InterpolateInfoForCar()
|
|
// PURPOSE : Uncompresses the matrix and stuff for a car to be played back
|
|
// in an animation.
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
void CVehicleRecordingMgr::InterpolateInfoForCar(CVehicle *pCar, CVehicleStateEachFrame *pCarState, float Interp, float playbackSpeed, const Quaternion& additionalRotation, Vec3V_In localPositionOffset, Vec3V_In globalPositionOffset, const Matrix34 &PreviousMatrix, const float fTimeStep)
|
|
{
|
|
Matrix34 TempMatr, ResultMatr;
|
|
Matrix34 TempMatr2 = MAT34V_TO_MATRIX34(pCar->GetMatrix());
|
|
|
|
RestoreInfoForMatrix(TempMatr, pCarState);
|
|
TempMatr.Scale(Interp);
|
|
TempMatr.d.x *= Interp;
|
|
TempMatr.d.y *= Interp;
|
|
TempMatr.d.z *= Interp;
|
|
|
|
TempMatr2.Scale(1.0f - Interp);
|
|
TempMatr2.d.x *= (1.0f - Interp);
|
|
TempMatr2.d.y *= (1.0f - Interp);
|
|
TempMatr2.d.z *= (1.0f - Interp);
|
|
|
|
ResultMatr.a = TempMatr.a+TempMatr2.a;
|
|
ResultMatr.b = TempMatr.b+TempMatr2.b;
|
|
ResultMatr.c = TempMatr.c+TempMatr2.c;
|
|
ResultMatr.d = TempMatr.d+TempMatr2.d;
|
|
ResultMatr.Normalize();
|
|
Assert(ResultMatr.IsOrthonormal());
|
|
|
|
if(additionalRotation.GetAngle() > SMALL_FLOAT)
|
|
{
|
|
Quaternion vehicleOrientation;
|
|
ResultMatr.ToQuaternion(vehicleOrientation);
|
|
|
|
vehicleOrientation.Multiply(additionalRotation);
|
|
|
|
ResultMatr.FromQuaternion(vehicleOrientation);
|
|
}
|
|
|
|
// Transform the position offset into world space and add it to the final matrxix
|
|
RC_MAT34V(ResultMatr).SetCol3(Add(RCC_MAT34V(ResultMatr).GetCol3(), Add(Transform3x3(RCC_MAT34V(ResultMatr),localPositionOffset),globalPositionOffset)));
|
|
|
|
const Matrix34 matOld = MAT34V_TO_MATRIX34(pCar->GetMatrix());
|
|
pCar->SetMatrix(ResultMatr, true, true);
|
|
HandleRecordingTeleport(pCar, matOld, false);
|
|
|
|
Vector3 vecSpeed;
|
|
vecSpeed.x = Interp * (pCarState->GetSpeedX() * playbackSpeed) + (1.0f - Interp) * pCar->GetVelocity().x;
|
|
vecSpeed.y = Interp * (pCarState->GetSpeedY() * playbackSpeed) + (1.0f - Interp) * pCar->GetVelocity().y;
|
|
vecSpeed.z = Interp * (pCarState->GetSpeedZ() * playbackSpeed) + (1.0f - Interp) * pCar->GetVelocity().z;
|
|
|
|
Assertf(!pCar->IsBaseFlagSet(fwEntity::IS_FIXED), "Can not play a recording on a car (%s) that is fixed", pCar->pHandling->m_handlingName.TryGetCStr());
|
|
|
|
//Ensure the magnitude does not exceed a certain threshold.
|
|
static ScalarV scMaxMagSq = ScalarVFromF32(rage::square(DEFAULT_MAX_SPEED));
|
|
ScalarV scMagSq = MagSquared(VECTOR3_TO_VEC3V(vecSpeed));
|
|
if(IsLessThanAll(scMagSq, scMaxMagSq))
|
|
{
|
|
pCar->SetVelocity(vecSpeed);
|
|
}
|
|
|
|
//Convert the time step from milliseconds to seconds.
|
|
ScalarV scTimeStep = Scale(ScalarVFromF32(fTimeStep), ScalarV(V_FLT_SMALL_3));
|
|
|
|
//Calculate the inverse time step.
|
|
ScalarV scInvTimeStep = InvertSafe(scTimeStep, ScalarV(V_ZERO));
|
|
|
|
//Calculate the angular velocity.
|
|
Vec3V vAngularVelocity = pCar->CalculateAngVelocityFromMatrices(RCC_MAT34V(PreviousMatrix), pCar->GetMatrix(), scInvTimeStep);
|
|
|
|
//Ensure the magnitude does not exceed a certain threshold.
|
|
//This is typically only the case when the vehicle is warping to the initial orientation.
|
|
scMagSq = MagSquared(vAngularVelocity);
|
|
scMaxMagSq = ScalarVFromF32(rage::square(DEFAULT_MAX_ANG_SPEED * 15.0f));
|
|
if(IsLessThanAll(scMagSq, scMaxMagSq))
|
|
{
|
|
//Set the angular velocity.
|
|
pCar->SetAngVelocity(VEC3V_TO_VECTOR3(vAngularVelocity));
|
|
}
|
|
|
|
//Interpolate the steer angle
|
|
pCar->m_vehControls.m_steerAngle = Interp * (pCarState->SteerAngle / 20.0f) + (1.0f - Interp) * pCar->m_vehControls.m_steerAngle;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : SkipForwardInRecording()
|
|
// PURPOSE :
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
void CVehicleRecordingMgr::SkipForwardInRecording(class CVehicle *pCar, float Dist)
|
|
{
|
|
s32 C;
|
|
CVehicleStateEachFrame *pOldState, *pNewState;
|
|
bool bDone = false;
|
|
|
|
C = 0;
|
|
while (C < MAX_CARS_PLAYBACK_NUMBER && ( (!bPlaybackGoingOn[C]) || pVehicleForPlayback[C] != pCar))
|
|
{
|
|
C++;
|
|
}
|
|
|
|
Assert(C < MAX_CARS_PLAYBACK_NUMBER);
|
|
if (C >= MAX_CARS_PLAYBACK_NUMBER)
|
|
return;
|
|
|
|
// Set this to the current state in case nothing changes.
|
|
pNewState = (CVehicleStateEachFrame *)&(pPlaybackBuffer[C][PlaybackIndex[C]]);
|
|
|
|
while (!bDone)
|
|
{
|
|
if (Dist > 0.0f)
|
|
{
|
|
if (PlaybackIndex[C] >= PlaybackBufferSize[C] - (s32)sizeof(CVehicleStateEachFrame))
|
|
{ // we're at the end of the recording. Call it a day.
|
|
bDone = true;
|
|
}
|
|
else
|
|
{
|
|
pOldState = (CVehicleStateEachFrame *)&(pPlaybackBuffer[C][PlaybackIndex[C]]);
|
|
PlaybackIndex[C] += sizeof(CVehicleStateEachFrame);
|
|
pNewState = (CVehicleStateEachFrame *)&(pPlaybackBuffer[C][PlaybackIndex[C]]);
|
|
Dist -= rage::Sqrtf( (pOldState->CoorsX - pNewState->CoorsX)*(pOldState->CoorsX - pNewState->CoorsX) +
|
|
(pOldState->CoorsY - pNewState->CoorsY)*(pOldState->CoorsY - pNewState->CoorsY) );
|
|
if (Dist <= 0.0f) bDone = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (PlaybackIndex[C] <= (s32)(sizeof(CVehicleStateEachFrame)))
|
|
{ // we're at the end of the recording. Call it a day.
|
|
bDone = true;
|
|
}
|
|
else
|
|
{
|
|
pOldState = (CVehicleStateEachFrame *)&(pPlaybackBuffer[C][PlaybackIndex[C]]);
|
|
PlaybackIndex[C] -= sizeof(CVehicleStateEachFrame);
|
|
pNewState = (CVehicleStateEachFrame *)&(pPlaybackBuffer[C][PlaybackIndex[C]]);
|
|
Dist += rage::Sqrtf( (pOldState->CoorsX - pNewState->CoorsX)*(pOldState->CoorsX - pNewState->CoorsX) +
|
|
(pOldState->CoorsY - pNewState->CoorsY)*(pOldState->CoorsY - pNewState->CoorsY) );
|
|
if (Dist >= 0.0f) bDone = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
PlaybackRunningTime[C] = (float)pNewState->TimeInRecording;
|
|
|
|
if (bUseCarAI[C])
|
|
{ // Actually place the car exactly on the path
|
|
pNewState = (CVehicleStateEachFrame *) &(pPlaybackBuffer[C][PlaybackIndex[C]]);
|
|
Matrix34 PreviousMatrix = MAT34V_TO_MATRIX34(pVehicleForPlayback[C]->GetMatrix());
|
|
|
|
float playbackSpeed;
|
|
if(bPlaybackPaused[C])
|
|
{
|
|
playbackSpeed = 0.0f;
|
|
}
|
|
#if __DEV
|
|
// If this is the debug recording as triggered by the widgets; allow user to change the playback speed with the slider.
|
|
else if(debug_WidgetRecordingPlaybackVehicle == pVehicleForPlayback[C] && pPlaybackBuffer[C] == debug_pDebugBuffer)
|
|
{
|
|
playbackSpeed = debug_timeScale;
|
|
}
|
|
#endif
|
|
else
|
|
{
|
|
playbackSpeed = PlaybackSpeed[C];
|
|
}
|
|
|
|
CVehicleRecordingMgr::RestoreInfoForCar(pVehicleForPlayback[C], pNewState, false, playbackSpeed, true);
|
|
|
|
// Code needed to stop the wheels applying silly forces.
|
|
for (s32 i = 0; i < pVehicleForPlayback[C]->GetNumWheels(); i++)
|
|
{
|
|
pVehicleForPlayback[C]->GetWheel(i)->UpdateContactsAfterNetworkBlend(PreviousMatrix, MAT34V_TO_MATRIX34(pVehicleForPlayback[C]->GetMatrix()));
|
|
}
|
|
}
|
|
|
|
aiVechicleRecordingFlags[C] |= VRF_FirstUpdate;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : SkipTimeForwardInRecording()
|
|
// PURPOSE :
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
void CVehicleRecordingMgr::SkipTimeForwardInRecording(class CVehicle *pCar, float Time)
|
|
{
|
|
s32 C = 0;
|
|
|
|
while (C < MAX_CARS_PLAYBACK_NUMBER && ( (!bPlaybackGoingOn[C]) || pVehicleForPlayback[C] != pCar))
|
|
{
|
|
C++;
|
|
}
|
|
|
|
if (vehicleVerifyf(C < MAX_CARS_PLAYBACK_NUMBER, "Couldn't find recording to skip"))
|
|
{
|
|
PlaybackRunningTime[C] += Time;
|
|
|
|
// Clip time to between 0 and the end of the recording.
|
|
PlaybackRunningTime[C] = rage::Max(0.0f, PlaybackRunningTime[C]);
|
|
|
|
u32 maxTime = ((CVehicleStateEachFrame*)&pPlaybackBuffer[C][PlaybackBufferSize[C] - sizeof(CVehicleStateEachFrame)])->TimeInRecording;
|
|
PlaybackRunningTime[C] = rage::Min( ((float)maxTime), PlaybackRunningTime[C]);
|
|
|
|
aiVechicleRecordingFlags[C] |= VRF_FirstUpdate;
|
|
}
|
|
|
|
}
|
|
u32 CVehicleRecordingMgr::GetEndTimeOfRecording(int index)
|
|
{
|
|
Assertf( index < MAX_CARS_PLAYBACK_NUMBER, "Bad car playback index");
|
|
return ((CVehicleStateEachFrame*)&pPlaybackBuffer[index][PlaybackBufferSize[index] - sizeof(CVehicleStateEachFrame)])->TimeInRecording;
|
|
}
|
|
|
|
float CVehicleRecordingMgr::GetPlaybackRunningTime(int index)
|
|
{
|
|
Assertf( index < MAX_CARS_PLAYBACK_NUMBER, "Bad car playback index");
|
|
return PlaybackRunningTime[index];
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : SetAdditionalRotationForRecordedVehicle()
|
|
// PURPOSE : This function allows the script to apply an additional
|
|
// rotation to a vehicle that is playing back a recording.
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
void CVehicleRecordingMgr::SetAdditionalRotationForRecordedVehicle(class CVehicle *pVehicle, Vec3V_In axisAngleRotation)
|
|
{
|
|
s32 C = 0;
|
|
|
|
while (C < MAX_CARS_PLAYBACK_NUMBER && ( (!bPlaybackGoingOn[C]) || pVehicleForPlayback[C] != pVehicle))
|
|
{
|
|
C++;
|
|
}
|
|
|
|
if (vehicleVerifyf(C < MAX_CARS_PLAYBACK_NUMBER, "SetAdditionalRotationForRecordedVehicle: This vehicle doesn't have a playback running on it"))
|
|
{
|
|
PlaybackAdditionalRotation[C] = axisAngleRotation;
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : SetLocalPositionOffsetForRecordedVehicle()
|
|
// PURPOSE : This function allows the script to apply a local
|
|
// position offset to a vehicle that is playing back a recording.
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
void CVehicleRecordingMgr::SetLocalPositionOffsetForRecordedVehicle(class CVehicle *pVehicle, Vec3V_In localOffset)
|
|
{
|
|
s32 C = 0;
|
|
|
|
while (C < MAX_CARS_PLAYBACK_NUMBER && ( (!bPlaybackGoingOn[C]) || pVehicleForPlayback[C] != pVehicle))
|
|
{
|
|
C++;
|
|
}
|
|
|
|
if (vehicleVerifyf(C < MAX_CARS_PLAYBACK_NUMBER, "SetAdditionalPositionForRecordingVehicle: This vehicle doesn't have a playback running on it"))
|
|
{
|
|
PlaybackLocalPositionOffset[C] = localOffset;
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : SetGlobalPositionOffsetForRecordedVehicle()
|
|
// PURPOSE : This function allows the script to apply a global
|
|
// position offset to a vehicle that is playing back a recording.
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
void CVehicleRecordingMgr::SetGlobalPositionOffsetForRecordedVehicle(class CVehicle *pVehicle, Vec3V_In globalOffset)
|
|
{
|
|
s32 C = 0;
|
|
|
|
while (C < MAX_CARS_PLAYBACK_NUMBER && ( (!bPlaybackGoingOn[C]) || pVehicleForPlayback[C] != pVehicle))
|
|
{
|
|
C++;
|
|
}
|
|
|
|
if (vehicleVerifyf(C < MAX_CARS_PLAYBACK_NUMBER, "SetAdditionalPositionForRecordingVehicle: This vehicle doesn't have a playback running on it"))
|
|
{
|
|
PlaybackGlobalPositionOffset[C] = globalOffset;
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : FindPositionInRecording()
|
|
// PURPOSE : This function allows the script to find out how far into
|
|
// the recording it is. The distance returned is in meters from
|
|
// the start.
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
float CVehicleRecordingMgr::FindPositionInRecording(const CVehicle *pCar)
|
|
{
|
|
s32 C;
|
|
CVehicleStateEachFrame *pLoopState, *pCurrentState, *pNextState;
|
|
|
|
C = 0;
|
|
while (C < MAX_CARS_PLAYBACK_NUMBER && ( (!bPlaybackGoingOn[C]) || pVehicleForPlayback[C] != pCar))
|
|
{
|
|
C++;
|
|
}
|
|
|
|
Assertf(C < MAX_CARS_PLAYBACK_NUMBER, "FindPositionInRecording: This car doesn't have a playback running on it");
|
|
if (C >= MAX_CARS_PLAYBACK_NUMBER)
|
|
return 0.0f;
|
|
|
|
// Set this to the current state in case nothing changes.
|
|
pCurrentState = (CVehicleStateEachFrame *)&(pPlaybackBuffer[C][PlaybackIndex[C]]);
|
|
pLoopState = (CVehicleStateEachFrame *)&(pPlaybackBuffer[C][0]);
|
|
float TotalDistance = 0;
|
|
|
|
// Go through all the states and calculate the total distance.
|
|
while (pCurrentState != pLoopState)
|
|
{
|
|
pNextState = pLoopState + 1;
|
|
|
|
TotalDistance += rage::Sqrtf( (pNextState->CoorsX - pLoopState->CoorsX)*(pNextState->CoorsX - pLoopState->CoorsX) +
|
|
(pNextState->CoorsY - pLoopState->CoorsY)*(pNextState->CoorsY - pLoopState->CoorsY) );
|
|
|
|
pLoopState = pNextState;
|
|
|
|
Assert((void*)pNextState <= (void*)&(pPlaybackBuffer[C][PlaybackBufferSize[C]]));
|
|
}
|
|
|
|
return TotalDistance;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : FindTimePositionInRecording()
|
|
// PURPOSE : This function allows the script to find out how far into
|
|
// the recording it is. The distance returned is in milliseconds from
|
|
// the start.
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
float CVehicleRecordingMgr::FindTimePositionInRecording(const CVehicle *pCar)
|
|
{
|
|
s32 C;
|
|
|
|
C = 0;
|
|
while (C < MAX_CARS_PLAYBACK_NUMBER && ( (!bPlaybackGoingOn[C]) || pVehicleForPlayback[C] != pCar))
|
|
{
|
|
C++;
|
|
}
|
|
|
|
Assertf(C < MAX_CARS_PLAYBACK_NUMBER, "FindTimePositionInRecording: This car doesn't have a playback running on it");
|
|
if (C < MAX_CARS_PLAYBACK_NUMBER)
|
|
return PlaybackRunningTime[C];
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : SetRecordingToPointNearestToCoors()
|
|
// PURPOSE : This function allows the script to find out how far into
|
|
// the recording it is. The distance returned is in milliseconds from
|
|
// the start. This command applies to the recording being recorded at the moment.
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
void CVehicleRecordingMgr::SetRecordingToPointNearestToCoors(class CVehicle *pCar, Vector3 *pPos)
|
|
{
|
|
|
|
s32 C;
|
|
|
|
C = 0;
|
|
while (C < MAX_CARS_PLAYBACK_NUMBER && ( (!bPlaybackGoingOn[C]) || pVehicleForPlayback[C] != pCar))
|
|
{
|
|
C++;
|
|
}
|
|
|
|
Assertf(C < MAX_CARS_PLAYBACK_NUMBER, "SetRecordingToPointNearestToCoors: This car doesn't have a playback running on it");
|
|
if (C >= MAX_CARS_PLAYBACK_NUMBER)
|
|
return;
|
|
|
|
SetRecordingToPointClosestToCoors(C, *pPos);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : GetTransformOfCarRecordingAtTime()
|
|
// PURPOSE : Helper function that gets the full transform at a given time
|
|
// in the recording.
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
void CVehicleRecordingMgr::GetTransformOfCarRecordingAtTime(int index, float time, Matrix34 &RetVal )
|
|
{
|
|
Assert(index >= 0 && index < GetMaxNumOfPlaybackFiles());
|
|
if (!aiVerifyf(sm_StreamingArray[index].HasRecordingData(), "GET_TRANSFORM_OF_CAR_RECORDING_AT_TIME is looking at a recording that is not streamed in" ))
|
|
{
|
|
RetVal.Identity();
|
|
return;
|
|
}
|
|
|
|
sm_StreamingArray[index].GetTransformOfCarRecordingAtTime(time, RetVal);
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : GetPositionOfCarRecordingAtTime()
|
|
// PURPOSE : This function will returns the position in a recording so that the
|
|
// level designers can make sure they place cars that are not in view
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
void CVehicleRecordingMgr::GetPositionOfCarRecordingAtTime(int index, float time, Vector3 &RetVal)
|
|
{
|
|
Assert(index >= 0 && index < GetMaxNumOfPlaybackFiles());
|
|
if (!aiVerifyf(sm_StreamingArray[index].HasRecordingData(), "GET_POSITION_OF_CAR_RECORDING_AT_TIME is looking at a recording that is not streamed in" ))
|
|
{
|
|
RetVal.Zero();
|
|
return;
|
|
}
|
|
|
|
sm_StreamingArray[index].GetPositionOfCarRecordingAtTime(time, RetVal);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : GetRotationOfCarRecordingAtTime()
|
|
// PURPOSE : This function will returns the rotation in a recording so that the
|
|
// level designers can get access to YPR info
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
void CVehicleRecordingMgr::GetRotationOfCarRecordingAtTime(int index, float time, Vector3 &RetVal)
|
|
{
|
|
Matrix34 mat;
|
|
GetTransformOfCarRecordingAtTime( index, time, mat );
|
|
// We want this to match get_entity_rotation
|
|
RetVal = CScriptEulers::MatrixToEulers(mat, EULER_YXZ);
|
|
RetVal *= RtoD;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : GetTotalDurationOfCarRecording()
|
|
// PURPOSE : Returns the total duration in milliseconds of a car recording
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
float CVehicleRecordingMgr::GetTotalDurationOfCarRecording(int index)
|
|
{
|
|
Assert(index >= 0 && index < GetMaxNumOfPlaybackFiles());
|
|
Assertf(sm_StreamingArray[index].HasRecordingData(), "GET_TOTAL_DURATION_OF_CAR_RECORDING is looking at a recording that is not streamed in" );
|
|
|
|
if( index >= 0 &&
|
|
index < GetMaxNumOfPlaybackFiles() &&
|
|
sm_StreamingArray[ index ].HasRecordingData() )
|
|
{
|
|
return sm_StreamingArray[ index ].GetTotalDurationOfCarRecording();
|
|
}
|
|
|
|
return 0.0f;
|
|
}
|
|
|
|
|
|
#if !__FINAL
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : FindTimePositionInRecordedRecording()
|
|
// PURPOSE : This function allows the script to find out how far into
|
|
// the recording it is. The distance returned is in milliseconds from
|
|
// the start. This command applies to the recording being recorded at the moment.
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
float CVehicleRecordingMgr::FindTimePositionInRecordedRecording(const class CVehicle *pCar)
|
|
{
|
|
s32 C;
|
|
|
|
C = 0;
|
|
while(C < MAX_CARS_RECORDED_NUMBER && ( (!ms_vehiclesRecordings[C].IsRecording() || ms_vehiclesRecordings[C].GetVehicle() != pCar)))
|
|
{
|
|
C++;
|
|
}
|
|
|
|
Assertf(C < MAX_CARS_RECORDED_NUMBER, "FindPositionInRecordedRecording: This car doesn't have a playback running on it");
|
|
|
|
return ms_vehiclesRecordings[C].FindTimePositionInRecording();
|
|
}
|
|
#endif
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : TryToTurnAIRecordingBackIntoFull()
|
|
// PURPOSE : If so specified by the level designer an car recording that is using ai (not a strict one)
|
|
// will try to revert to being a full recording if the car is close enough to it.
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
void CVehicleRecordingMgr::TryToTurnAIRecordingBackIntoFull(s32 C)
|
|
{
|
|
CVehicle *pVeh = pVehicleForPlayback[C];
|
|
Vector3 Coors = VEC3V_TO_VECTOR3(pVeh->GetVehiclePosition());
|
|
// First we find the point nearest the vehicle on the recording that we consider latching on to.
|
|
s32 IndexInRecording = 0;
|
|
s32 NearestIndexInRecording = -1;
|
|
u32 NearestPlaybackRunningTime = 0;
|
|
float fSpeedSqrAtTimeInRecording = -1.0f;
|
|
|
|
// Extend the switch back time whilst still in contact with vehicles
|
|
if(pVeh->GetFrameCollisionHistory()->HasCollidedWithAnyOfTypes(ENTITY_TYPE_MASK_VEHICLE))
|
|
{
|
|
TurnBackIntoFullPlayBackTime[C] = rage::Max(fwTimer::GetTimeInMilliseconds()+500, TurnBackIntoFullPlayBackTime[C]);
|
|
}
|
|
|
|
if( fwTimer::GetTimeInMilliseconds() < TurnBackIntoFullPlayBackTime[C] )
|
|
{
|
|
return;
|
|
}
|
|
|
|
float NearestDist = 999999.9f;
|
|
while (IndexInRecording < PlaybackBufferSize[C])
|
|
{
|
|
Vector3 vRot;
|
|
Matrix34 mat;
|
|
|
|
CVehicleStateEachFrame *pVehState = (CVehicleStateEachFrame *) &((pPlaybackBuffer[C])[IndexInRecording]);
|
|
|
|
RestoreInfoForMatrix( mat, pVehState);
|
|
mat.ToEulersXYZ( vRot );
|
|
|
|
const float fTheta = Abs(fwAngle::LimitRadianAngle(vRot.z - pVeh->GetTransform().GetHeading()));
|
|
if( fTheta <= (EIGHTH_PI / 2.0f) ) //one-sixteenth pi
|
|
{
|
|
float Distance = (Coors - Vector3(pVehState->CoorsX, pVehState->CoorsY, pVehState->CoorsZ) ).Mag();
|
|
if (Distance < NearestDist)
|
|
{
|
|
NearestIndexInRecording = IndexInRecording;
|
|
NearestPlaybackRunningTime = pVehState->TimeInRecording;
|
|
NearestDist = Distance;
|
|
const Vector3 vVelocityAtTimeInRecording(pVehState->GetSpeedX(), pVehState->GetSpeedY(), pVehState->GetSpeedZ());
|
|
float playbackSpeed = GetPlaybackSpeed(C);
|
|
fSpeedSqrAtTimeInRecording = vVelocityAtTimeInRecording.Mag2()*(playbackSpeed*playbackSpeed);
|
|
}
|
|
}
|
|
|
|
IndexInRecording += sizeof(CVehicleStateEachFrame);
|
|
}
|
|
|
|
//only join back to the recording if our velocity within some acceptable
|
|
//threshold from the target velocity, otherwise we'll notice a visible
|
|
//immediate change in velocity
|
|
const Vector3& velocity = pVeh->GetVelocity();
|
|
const float actualSpeedSqr = velocity.Mag2();
|
|
static dev_float s_fSpeedThresholdSqr = 0.5f * 0.5f;
|
|
const bool bGoingFastEnough = actualSpeedSqr > fSpeedSqrAtTimeInRecording * s_fSpeedThresholdSqr;
|
|
|
|
if (NearestDist < 0.5f && bGoingFastEnough)
|
|
{ // Let's go for it. We turn the recording back into a full recording.
|
|
PlaybackIndex[C] = NearestIndexInRecording;
|
|
PlaybackRunningTime[C] = (float)NearestPlaybackRunningTime;
|
|
TurnBackIntoFullPlayBackTime[C] = 0;
|
|
bUseCarAI[C] = false;
|
|
pVehicleForPlayback[C]->m_nVehicleFlags.bLerpToFullRecording = pVehicleForPlayback[C]->m_nVehicleFlags.bShouldLerpFromAiToFullRecording;
|
|
}
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : Render()
|
|
// PURPOSE : This function will render the recording as a bunch of lines.
|
|
// Should be handy for debugging and also for certain missions (like
|
|
// showing how the player should fly a stunt)
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
void CVehicleRecordingMgr::Render()
|
|
{
|
|
|
|
#if DEBUG_DRAW
|
|
u32 PlayBackIndex, NextPlayBackIndex;
|
|
CVehicleStateEachFrame *pVehState;
|
|
s32 OffsetBehind, OffsetInFront;
|
|
bool bPreviousPointStored = false;
|
|
Vector3 vPreviousPointStored;
|
|
|
|
for (s32 C = 0; C < MAX_CARS_PLAYBACK_NUMBER; C++)
|
|
{
|
|
if (bPlaybackGoingOn[C])
|
|
{
|
|
#if __BANK // DW - unfortunate fix for compile error in profile build... could be better if just use __BANK?
|
|
#if !__FINAL
|
|
if (DisplayMode[C] != RDM_NONE || bRenderAllRecordings)
|
|
#else
|
|
if (DisplayMode[C] != RDM_NONE)
|
|
#endif
|
|
#else
|
|
if (DisplayMode[C] != RDM_NONE)
|
|
#endif
|
|
|
|
{
|
|
s32 DisplayModeFinal = DisplayMode[C];
|
|
|
|
#if __BANK // DW - unfortunate fix for compile error in profile build... could be better if just use __BANK?
|
|
#if !__FINAL
|
|
if (bRenderAllRecordings && audVehicleAudioEntity::ShouldRenderRecording(C))
|
|
{
|
|
DisplayModeFinal = RDM_WHOLELINE;
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
Color32 LineColour;
|
|
if (C < FOR_CODE_INDEX_LAST)
|
|
{
|
|
LineColour = Color32(255, 255, 0, 255); // This one is for a code recording (buses)
|
|
}
|
|
else
|
|
{
|
|
LineColour = Color32(255, 0, 0, 255);
|
|
}
|
|
|
|
switch(DisplayModeFinal)
|
|
{
|
|
case RDM_WHOLELINE:
|
|
// Set the appropriate rendermode
|
|
PlayBackIndex = 0;
|
|
while (PlayBackIndex < PlaybackBufferSize[C] - sizeof(CVehicleStateEachFrame))
|
|
{
|
|
NextPlayBackIndex = PlayBackIndex + sizeof(CVehicleStateEachFrame);
|
|
pVehState = (CVehicleStateEachFrame *) &((CVehicleRecordingMgr::pPlaybackBuffer[C])[PlayBackIndex]);
|
|
|
|
// Draw the line segment between this point and the last.
|
|
if (bPreviousPointStored)
|
|
{
|
|
grcDebugDraw::Line(vPreviousPointStored,
|
|
Vector3(pVehState->CoorsX, pVehState->CoorsY, pVehState->CoorsZ),
|
|
LineColour);
|
|
}
|
|
vPreviousPointStored.x = pVehState->CoorsX;
|
|
vPreviousPointStored.y = pVehState->CoorsY;
|
|
vPreviousPointStored.z = pVehState->CoorsZ;
|
|
bPreviousPointStored = true;
|
|
|
|
|
|
|
|
PlayBackIndex = NextPlayBackIndex;
|
|
}
|
|
break;
|
|
|
|
case RDM_JUSTINFRONT:
|
|
case RDM_JUSTBEHIND:
|
|
case RDM_AROUNDVEHICLE:
|
|
#define SAMPLESAROUND (20)
|
|
OffsetBehind = OffsetInFront = 0;
|
|
|
|
if (DisplayModeFinal == RDM_JUSTBEHIND || DisplayModeFinal == RDM_AROUNDVEHICLE)
|
|
{
|
|
OffsetBehind = -SAMPLESAROUND;
|
|
}
|
|
if (DisplayModeFinal == RDM_JUSTINFRONT || DisplayModeFinal == RDM_AROUNDVEHICLE)
|
|
{
|
|
OffsetInFront = SAMPLESAROUND;
|
|
}
|
|
|
|
for (s32 Samples = OffsetBehind; Samples < OffsetInFront; Samples++)
|
|
{
|
|
s32 PointPlayBackIndex = PlaybackIndex[C];
|
|
PointPlayBackIndex += Samples * sizeof(CVehicleStateEachFrame);
|
|
if (PointPlayBackIndex > 0 && PointPlayBackIndex < PlaybackBufferSize[C])
|
|
{
|
|
float FadeVal = 1.0f - (ABS(Samples) / float(SAMPLESAROUND));
|
|
|
|
pVehState = (CVehicleStateEachFrame *) &((CVehicleRecordingMgr::pPlaybackBuffer[C])[PointPlayBackIndex]);
|
|
|
|
if (bPreviousPointStored)
|
|
{
|
|
grcDebugDraw::Line(vPreviousPointStored,
|
|
Vector3(pVehState->CoorsX, pVehState->CoorsY, pVehState->CoorsZ),
|
|
Color32((u8)(100 * FadeVal), (u8)(100 * FadeVal), (u8)(50 * FadeVal), 200));
|
|
}
|
|
vPreviousPointStored.x = pVehState->CoorsX;
|
|
vPreviousPointStored.y = pVehState->CoorsY;
|
|
vPreviousPointStored.z = pVehState->CoorsZ;
|
|
bPreviousPointStored = true;
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif // DEBUG_DRAW
|
|
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : FindRecordingFile()
|
|
// PURPOSE : This function is used by the CVehicleRecordingStreamingModule::FindSlot
|
|
// it will return -1 if not found
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
s32 CVehicleRecordingMgr::FindRecordingFile(const char *pFilename)
|
|
{
|
|
s32 index;
|
|
|
|
Assertf(strlen(pFilename) < CAR_RECORDING_NAME_LENGTH, "%s:RecordingFileName too long", pFilename);
|
|
|
|
CVehicleRecordingStreaming* pRecordingInfo = CVehicleRecordingMgr::GetRecordingInfo(pFilename, &index);
|
|
|
|
if(pRecordingInfo)//there is already a car recording with this name
|
|
return index;
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : RegisterRecordingFile()
|
|
// PURPOSE : This function is called by Adam and will inform the record code that
|
|
// a file has been found in the .img file.
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
s32 CVehicleRecordingMgr::RegisterRecordingFile(const char *pFilename)
|
|
{
|
|
s32 index;
|
|
|
|
Assertf(strlen(pFilename) < CAR_RECORDING_NAME_LENGTH, "%s:RecordingFileName too long", pFilename);
|
|
|
|
CVehicleRecordingStreaming* pRecordingInfo = CVehicleRecordingMgr::GetRecordingInfo(pFilename, &index);
|
|
|
|
if(pRecordingInfo)//there is already a car recording with this name
|
|
return index;
|
|
|
|
Assertf(sm_NumPlayBackFiles < GetMaxNumOfPlaybackFiles(), "Ran out of Recordinginfos (%d)", GetMaxNumOfPlaybackFiles());
|
|
|
|
if ( !(sm_NumPlayBackFiles < GetMaxNumOfPlaybackFiles()) )
|
|
return -1;
|
|
|
|
sm_StreamingArray[sm_NumPlayBackFiles].RegisterRecordingFile(pFilename);
|
|
|
|
// Add recordinginfo hash key into map (duplicates are caught above)
|
|
Assertf(sm_RecordInfoMap.Lookup(sm_StreamingArray[sm_NumPlayBackFiles].GetHashKey()) == -1,"Vehicle recording %s is duplicated",pFilename);
|
|
sm_RecordInfoMap.Insert(sm_StreamingArray[sm_NumPlayBackFiles].GetHashKey(),sm_NumPlayBackFiles);
|
|
sm_NumPlayBackFiles++;
|
|
|
|
return sm_NumPlayBackFiles-1;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : RemoveSlot()
|
|
// PURPOSE : Eliminate the slot and make it available again.
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
void CVehicleRecordingMgr::RemoveSlot(int index)
|
|
{
|
|
sm_RecordInfoMap.Delete(sm_StreamingArray[index].GetHashKey());
|
|
sm_StreamingArray[index].Invalidate();
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : FindIndexWithFileNameNumber()
|
|
// PURPOSE : Given the number of the filename carrec012.rrr this will find the
|
|
// index into the streaming array.
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
s32 CVehicleRecordingMgr::FindIndexWithFileNameNumber(s32 ArgFileNameNumber, const char* pRecordingName)
|
|
{
|
|
char fileName[64];
|
|
sprintf(fileName, "%s%03d", pRecordingName, ArgFileNameNumber);
|
|
s32 index = GetRecordingIndex(fileName);
|
|
return index;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : HasRecordingFileBeenLoaded()
|
|
// PURPOSE : To be called by the level designers so that they can work out
|
|
// whether a recording file has been streamed in.
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
bool CVehicleRecordingMgr::HasRecordingFileBeenLoaded(int index)
|
|
{
|
|
if(index != -1)
|
|
return (sm_StreamingArray[index].HasRecordingData());
|
|
return false;
|
|
}
|
|
|
|
|
|
//
|
|
// name: CVehicleRecordingMgr::Remove
|
|
// description: Remove vehicle recording
|
|
void CVehicleRecordingMgr::Remove(s32 index)
|
|
{
|
|
sm_StreamingArray[index].Remove();
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : SmoothRecording()
|
|
// PURPOSE : Recordings can be a bit jittery when recorded. This function smoothes
|
|
// the time steps out a little bit.
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
/*void CVehicleRecordingMgr::SmoothRecording(s32 Index)
|
|
{
|
|
u8 *pRecording = sm_StreamingArray[Index].pRecordingData;
|
|
Assert(pRecording);
|
|
|
|
s32 IndexInArray = sizeof(CVehicleStateEachFrame);
|
|
while (IndexInArray < sm_StreamingArray[Index].RecordingDataSize - (s32)sizeof(CVehicleStateEachFrame))
|
|
{
|
|
CVehicleStateEachFrame *pOldState = (CVehicleStateEachFrame *) &(pRecording[IndexInArray-sizeof(CVehicleStateEachFrame)]);
|
|
CVehicleStateEachFrame *pCurrentState = (CVehicleStateEachFrame *) &(pRecording[IndexInArray]);
|
|
CVehicleStateEachFrame *pNewState = (CVehicleStateEachFrame *) &(pRecording[IndexInArray+sizeof(CVehicleStateEachFrame)]);
|
|
|
|
pCurrentState->TimeInRecording = (u32)((pOldState->TimeInRecording + pNewState->TimeInRecording) * 0.5f);
|
|
|
|
IndexInArray += sizeof(CVehicleStateEachFrame);
|
|
}
|
|
}*/
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : FindPlaybackRunningTimeAccordingToSchedule()
|
|
// PURPOSE : Depending on the time of day and the schedule of this recording this
|
|
// function calculates the current position within the recording.
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
u32 CVehicleRecordingMgr::FindPlaybackRunningTimeAccordingToSchedule(s32 Recording, u32 MillisecondsOffset)
|
|
{
|
|
// Calculate the milliseconds that have passed since midnight.
|
|
int MinutesInRoute = CClock::GetMinutesSinceMidnight() + Recording * 3;
|
|
#define LOOP_TIME (1 * 60) // This route loops every 1 hour
|
|
while (MinutesInRoute > LOOP_TIME)
|
|
{
|
|
MinutesInRoute -= LOOP_TIME;
|
|
}
|
|
|
|
u32 MilliSecondsInRoute = (u32)(MinutesInRoute * CClock::GetMsPerGameMinute());
|
|
MilliSecondsInRoute += MillisecondsOffset;
|
|
|
|
Assert(pPlaybackBuffer[Recording]); // Make sure recording has been loaded.
|
|
u32 RecordingLength = (((CVehicleStateEachFrame *)&pPlaybackBuffer[Recording][PlaybackBufferSize[Recording]])-1)->TimeInRecording;
|
|
|
|
MilliSecondsInRoute = rage::Min(MilliSecondsInRoute, RecordingLength);
|
|
|
|
return MilliSecondsInRoute;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : FindTimeJumpRequiredToGetToNextStaticPoint()
|
|
// PURPOSE : Finds out how nuch time needs to expire get to the next point in this
|
|
// recording where the vehicle is static.
|
|
// This could be the next bus stop.
|
|
// RETURNS : Time jump in game minutes
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
s32 CVehicleRecordingMgr::FindTimeJumpRequiredToGetToNextStaticPoint(s32 C)
|
|
{
|
|
s32 PlayBackIndex = CVehicleRecordingMgr::PlaybackIndex[C];
|
|
s32 PlayBackIndexAhead;
|
|
CVehicleStateEachFrame *pVehStateAhead = NULL;
|
|
// We consider the vehicle static if it doesn't move more than a meter in 5 seconds.
|
|
s32 Attempts = PlaybackBufferSize[C] / sizeof(CVehicleStateEachFrame);
|
|
|
|
while (Attempts > 0)
|
|
{
|
|
PlayBackIndex += sizeof(CVehicleStateEachFrame);
|
|
if (PlayBackIndex >= PlaybackBufferSize[C])
|
|
{
|
|
PlayBackIndex = 0;
|
|
}
|
|
CVehicleStateEachFrame *pVehState = (CVehicleStateEachFrame *) &((pPlaybackBuffer[C])[PlayBackIndex]);
|
|
|
|
// Find the point in the recording that is 5 meter ahead.
|
|
PlayBackIndexAhead = PlayBackIndex;
|
|
|
|
s32 TimeToBridge = 5000;
|
|
s32 RunningTime = pVehState->TimeInRecording;
|
|
|
|
while (TimeToBridge > 0)
|
|
{
|
|
PlayBackIndexAhead += sizeof(CVehicleStateEachFrame);
|
|
if (PlayBackIndexAhead >= PlaybackBufferSize[C])
|
|
{
|
|
PlayBackIndexAhead = 0;
|
|
RunningTime = 0;
|
|
}
|
|
|
|
pVehStateAhead = (CVehicleStateEachFrame *) &((pPlaybackBuffer[C])[PlayBackIndexAhead]);
|
|
|
|
TimeToBridge -= pVehStateAhead->TimeInRecording - RunningTime;
|
|
RunningTime = pVehStateAhead->TimeInRecording;
|
|
}
|
|
Vector3 VecNow = Vector3(pVehState->CoorsX, pVehState->CoorsY, pVehState->CoorsZ);
|
|
Assert(pVehStateAhead);
|
|
Vector3 VecAhead = Vector3(pVehStateAhead->CoorsX, pVehStateAhead->CoorsY, pVehStateAhead->CoorsZ);
|
|
|
|
// If the coordinates haven't moved a meter or more we have found the next static bit in the recording
|
|
if ( (VecNow-VecAhead).Mag() < 1.0f)
|
|
{
|
|
CVehicleStateEachFrame *pVehStartState = (CVehicleStateEachFrame *) &((pPlaybackBuffer[C])[PlaybackIndex[C]]);
|
|
|
|
s32 ExpiredTimeInMilliseconds = pVehState->TimeInRecording - pVehStartState->TimeInRecording;
|
|
if (ExpiredTimeInMilliseconds < 0)
|
|
{
|
|
CVehicleStateEachFrame *pVehLastState = (CVehicleStateEachFrame *) &((pPlaybackBuffer[C])[PlaybackBufferSize[C] - sizeof(CVehicleStateEachFrame)]);
|
|
ExpiredTimeInMilliseconds += pVehLastState->TimeInRecording;
|
|
Assert(ExpiredTimeInMilliseconds >= 0);
|
|
|
|
// Find out how many game minutes correspond to the milliseconds found.
|
|
return ExpiredTimeInMilliseconds / CClock::GetMsPerGameMinute();
|
|
}
|
|
}
|
|
Attempts--;
|
|
}
|
|
Assertf(0, "FindTimeJumpRequiredToGetToNextStaticPoint didn't find a static point in the recording");
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
void Swap4Bytes(void *pPointer)
|
|
{
|
|
u8 *pPtr = (u8*)pPointer;
|
|
u8 Temp = pPtr[0];
|
|
pPtr[0] = pPtr[3];
|
|
pPtr[3] = Temp;
|
|
Temp = pPtr[1];
|
|
pPtr[1] = pPtr[2];
|
|
pPtr[2] = Temp;
|
|
}
|
|
|
|
void Swap2Bytes(void *pPointer)
|
|
{
|
|
u8 *pPtr = (u8*)pPointer;
|
|
u8 Temp = pPtr[0];
|
|
pPtr[0] = pPtr[1];
|
|
pPtr[1] = Temp;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : EndianSwap
|
|
// PURPOSE : Fixes the endian order from 360 to Pc and the other way around
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
void CVehicleStateEachFrame::EndianSwap()
|
|
{
|
|
Swap4Bytes(&TimeInRecording);
|
|
Swap4Bytes(&CoorsX);
|
|
Swap4Bytes(&CoorsY);
|
|
Swap4Bytes(&CoorsZ);
|
|
Swap2Bytes(&SpeedX);
|
|
Swap2Bytes(&SpeedY);
|
|
Swap2Bytes(&SpeedZ);
|
|
}
|
|
|
|
|
|
/////////////// Stuff from here has to do with converting files to ascii and back.
|
|
/////////////// The ascii files can be manipulated in Max.
|
|
#if !__FINAL
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : ConvertFilesToAscii()
|
|
// PURPOSE : Goes through all the carrecXXX.rrr files and converts them into
|
|
// carrecXXX.aaa (ascii)
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
/*void CVehicleRecordingMgr::ConvertFilesToAscii()
|
|
{
|
|
FileHandle fileIn;
|
|
FileHandle fileOut;
|
|
char FileNameIn[32];
|
|
char FileNameOut[32];
|
|
|
|
Displayf("Converting the carrec files from .rrr to .aaa.\n");
|
|
CFileMgr::SetDir(DEFAULT_RECORDING_PATH);
|
|
|
|
atArray<fiFindData*> results;
|
|
ASSET.EnumFiles(DEFAULT_RECORDING_PATH, CVehicleRecordingMgr::FindFileCallback, &results);
|
|
|
|
|
|
//for (s32 NameNumber = 0; NameNumber < HIGHESTFILENUMALLOWED; NameNumber++)
|
|
for(s32 i = 0 ; i < results.GetCount(); i++)
|
|
{
|
|
const char* fileNameAndExtension = ASSET.FileName(results[i]->m_Name);
|
|
char* pExtension = const_cast<char*>(ASSET.FindExtensionInPath(fileNameAndExtension));
|
|
|
|
if(pExtension != NULL && (stricmp(pExtension, ".rrr")) )
|
|
{
|
|
// Remove the extension leaving just the filename
|
|
char fileName[256];
|
|
ASSET.RemoveExtensionFromPath(fileName, 256, fileNameAndExtension);
|
|
|
|
//sprintf(FileNameIn, "carrec%03d.rrr", NameNumber);
|
|
//sprintf(FileNameOut, "carrec%03d.aaa", NameNumber);
|
|
sprintf(FileNameIn, "%s.rrr", fileName);
|
|
sprintf(FileNameOut, "%s.aaa", fileName);
|
|
|
|
fileIn = CFileMgr::OpenFile(FileNameIn);
|
|
if (CFileMgr::IsValidFileHandle(fileIn))
|
|
{ // File exists.
|
|
fileOut = CFileMgr::OpenFileForWriting(FileNameOut);
|
|
Assertf(CFileMgr::IsValidFileHandle(fileOut), "%s:Could not open file", FileNameOut);
|
|
ConvertFileFromRRRToAAA(fileIn, fileOut);
|
|
CFileMgr::CloseFile(fileOut);
|
|
CFileMgr::CloseFile(fileIn);
|
|
}
|
|
}
|
|
}
|
|
CFileMgr::SetDir("");
|
|
|
|
Displayf("Done.\n");
|
|
}
|
|
|
|
void CVehicleRecordingMgr::FindFileCallback(const fiFindData &data, void *user)
|
|
{
|
|
Assert(user);
|
|
atArray<fiFindData*> *results = reinterpret_cast<atArray<fiFindData*>*>(user);
|
|
results->PushAndGrow(rage_new fiFindData(data));
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : ConvertFilesFromAscii()
|
|
// PURPOSE : Goes through all the carrecXXX.aaa (ascii) files and converts them into
|
|
// carrecXXX.rrr (binary)
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
void CVehicleRecordingMgr::ConvertFilesFromAscii()
|
|
{
|
|
FileHandle fileIn;
|
|
FileHandle fileOut;
|
|
char FileNameIn[32];
|
|
char FileNameOut[32];
|
|
|
|
Displayf("Converting the carrec files from .aaa to .rrr.\n");
|
|
CFileMgr::SetDir(DEFAULT_RECORDING_PATH);
|
|
|
|
atArray<fiFindData*> results;
|
|
ASSET.EnumFiles(DEFAULT_RECORDING_PATH, CVehicleRecordingMgr::FindFileCallback, &results);
|
|
|
|
//for (s32 NameNumber = 0; NameNumber < HIGHESTFILENUMALLOWED; NameNumber++)
|
|
for(s32 i = 0 ; i < results.GetCount(); i++)
|
|
{
|
|
const char* fileNameAndExtension = ASSET.FileName(results[i]->m_Name);
|
|
char* pExtension = const_cast<char*>(ASSET.FindExtensionInPath(fileNameAndExtension));
|
|
|
|
if(pExtension != NULL && (stricmp(pExtension, ".aaa")) )
|
|
{
|
|
// Remove the extension leaving just the filename
|
|
char fileName[256];
|
|
ASSET.RemoveExtensionFromPath(fileName, 256, fileNameAndExtension);
|
|
|
|
//sprintf(FileNameIn, "carrec%03d.aaa", NameNumber);
|
|
//sprintf(FileNameOut, "carrec%03dnew.rrr", NameNumber);
|
|
sprintf(FileNameIn, "%s.aaa", fileName);
|
|
sprintf(FileNameOut, "%s.rrr", fileName);
|
|
|
|
fileIn = CFileMgr::OpenFile(FileNameIn);
|
|
if( CFileMgr::IsValidFileHandle(fileIn) )
|
|
{ // File exists.
|
|
fileOut = CFileMgr::OpenFileForWriting(FileNameOut);
|
|
Assertf(CFileMgr::IsValidFileHandle(fileOut), "%s:Could not open file", FileNameOut);
|
|
ConvertFileFromAAAToRRR(fileIn, fileOut);
|
|
CFileMgr::CloseFile(fileOut);
|
|
CFileMgr::CloseFile(fileIn);
|
|
}
|
|
}
|
|
}
|
|
CFileMgr::SetDir("");
|
|
|
|
Displayf("Done.\n");
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : ConvertFileFromRRRToAAA()
|
|
// PURPOSE : Converts just this one file.
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
void CVehicleRecordingMgr::ConvertFileFromRRRToAAA(FileHandle fileRRR, FileHandle fileAAA, bool bAlsoHeading)
|
|
{
|
|
CVehicleStateEachFrame VehState;
|
|
Matrix34 VehMatrix;
|
|
char writeString[1024];
|
|
|
|
while (CFileMgr::Read(fileRRR, (char *)&VehState, sizeof(CVehicleStateEachFrame)))
|
|
{
|
|
#if __BE
|
|
EndianSwapRecording((u8*)&VehState, sizeof(CVehicleStateEachFrame));
|
|
#endif
|
|
|
|
|
|
float TimeVal = VehState.TimeInRecording*0.001f;
|
|
sprintf(writeString, "%.4f\r\n", TimeVal);
|
|
CFileMgr::Write(fileAAA, writeString, strlen(writeString));
|
|
|
|
sprintf(writeString, "%.2f %.2f %.2f\r\n", VehState.CoorsX, VehState.CoorsY, VehState.CoorsZ);
|
|
CFileMgr::Write(fileAAA, writeString, strlen(writeString));
|
|
|
|
RestoreInfoForMatrix(VehMatrix, &VehState);
|
|
sprintf(writeString, "%.4f %.4f %.4f %.4f %.4f %.4f %.4f %.4f %.4f\r\n", VehMatrix.a.x, VehMatrix.b.x, VehMatrix.c.x, VehMatrix.a.y, VehMatrix.b.y, VehMatrix.c.y, VehMatrix.a.z, VehMatrix.b.z, VehMatrix.c.z);
|
|
CFileMgr::Write(fileAAA, writeString, strlen(writeString));
|
|
|
|
if (bAlsoHeading)
|
|
{
|
|
float fHeading = ( RtoD * rage::Atan2f(-VehMatrix.b.x, VehMatrix.b.y));
|
|
|
|
if (fHeading < 0.0f)
|
|
{
|
|
fHeading += 360.0f;
|
|
}
|
|
if (fHeading > 360.0f)
|
|
{
|
|
fHeading -= 360.0f;
|
|
}
|
|
|
|
sprintf(writeString, "%.4f\r\n", fHeading);
|
|
CFileMgr::Write(fileAAA, writeString, strlen(writeString));
|
|
}
|
|
|
|
sprintf(writeString, "%d %d %d %d %d %d %d\r\n", VehState.SteerAngle, VehState.Gas, VehState.Brake, VehState.HandBrake, VehState.SpeedX, VehState.SpeedY, VehState.SpeedZ);
|
|
CFileMgr::Write(fileAAA, writeString, strlen(writeString));
|
|
|
|
sprintf(writeString, "\n");
|
|
CFileMgr::Write(fileAAA, writeString, strlen(writeString));
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : ConvertFileFromAAAToRRR()
|
|
// PURPOSE : Converts just this one file.
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
void CVehicleRecordingMgr::ConvertFileFromAAAToRRR(FileHandle fileAAA, FileHandle fileRRR)
|
|
{
|
|
CVehicleStateEachFrame VehState;
|
|
Matrix34 VehMatrix;
|
|
float fTemp;
|
|
s32 iTemp1, iTemp2, iTemp3, iTemp4, iTemp5, iTemp6, iTemp7;
|
|
char tempString[1024];
|
|
|
|
while (CFileMgr::ReadLine(fileAAA, tempString, 1024))
|
|
{
|
|
// Deal with blank line
|
|
if (strlen(tempString) <= 0)
|
|
{
|
|
if (!CFileMgr::ReadLine(fileAAA, tempString, 1024))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
sscanf(tempString, "%f", &fTemp);
|
|
VehState.TimeInRecording = (s32)(1000.0f * fTemp);
|
|
|
|
CFileMgr::ReadLine(fileAAA, tempString, 1024);
|
|
sscanf(tempString, "%f %f %f", &VehState.CoorsX, &VehState.CoorsY, &VehState.CoorsZ);
|
|
|
|
CFileMgr::ReadLine(fileAAA, tempString, 1024);
|
|
sscanf(tempString, "%f %f %f %f %f %f %f %f %f", &VehMatrix.a.x, &VehMatrix.b.x, &VehMatrix.c.x, &VehMatrix.a.y, &VehMatrix.b.y, &VehMatrix.c.y, &VehMatrix.a.z, &VehMatrix.b.z, &VehMatrix.c.z);
|
|
StoreInfoForMatrix(&VehMatrix, &VehState);
|
|
|
|
CFileMgr::ReadLine(fileAAA, tempString, 1024);
|
|
sscanf(tempString, "%d %d %d %d %d %d %d\n", &iTemp1, &iTemp2, &iTemp3, &iTemp4, &iTemp5, &iTemp6, &iTemp7);
|
|
VehState.SteerAngle = (s8)(iTemp1);
|
|
VehState.Gas = (s8)(iTemp2);
|
|
VehState.Brake = (s8)(iTemp3);
|
|
VehState.HandBrake = (s8)(iTemp4);
|
|
VehState.SpeedX = (s16)(iTemp5);
|
|
VehState.SpeedY = (s16)(iTemp6);
|
|
VehState.SpeedZ = (s16)(iTemp7);
|
|
#if __BE
|
|
EndianSwapRecording((u8*)&VehState, sizeof(CVehicleStateEachFrame));
|
|
#endif
|
|
|
|
CFileMgr::Write(fileRRR, (char *)&VehState, sizeof(CVehicleStateEachFrame));
|
|
}
|
|
}
|
|
*/
|
|
|
|
#endif
|
|
|
|
|
|
//
|
|
//
|
|
//
|
|
s32 CVehicleRecordingMgr::GetRecordingIndex(const char* pName)
|
|
{
|
|
return GetRecordingIndexFromHashKey(atStringHash(pName));
|
|
}
|
|
|
|
//
|
|
//
|
|
//
|
|
s32 CVehicleRecordingMgr::GetRecordingIndexFromHashKey(u32 key)
|
|
{
|
|
return sm_RecordInfoMap.Lookup(key);
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// name: GetRecordInfo
|
|
// description: Get a pointer to a recordingInfo class from a name
|
|
//
|
|
CVehicleRecordingStreaming* CVehicleRecordingMgr::GetRecordingInfo(const char *pName, s32* pReturnIndex)
|
|
{
|
|
s32 idx = sm_RecordInfoMap.Lookup(atStringHash(pName));
|
|
if(idx!=-1)
|
|
{
|
|
if(pReturnIndex)
|
|
*pReturnIndex = idx;
|
|
return &sm_StreamingArray[idx];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// name: GetRecordingInfoFromHashKey
|
|
// description: Get recordinginfo pointer from a hash key identifier
|
|
CVehicleRecordingStreaming* CVehicleRecordingMgr::GetRecordingInfoFromHashKey(u32 key, s32* pReturnIndex)
|
|
{
|
|
s32 idx = sm_RecordInfoMap.Lookup(key);
|
|
if(idx!=-1)
|
|
{
|
|
if(pReturnIndex)
|
|
*pReturnIndex = idx;
|
|
return &sm_StreamingArray[idx];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
#if __BANK
|
|
#if !__FINAL
|
|
|
|
void CVehicleRecordingMgr::SetCurrentRecording(const char* name,u32 num)
|
|
{
|
|
formatf(ms_debug_RecordingNameToPlayWith,"%s",name);
|
|
ms_debug_RecordingNumToPlayWith = num;
|
|
}
|
|
|
|
void TidyOldDebugBuffer()
|
|
{
|
|
if (debug_pDebugBuffer) // Tidy up the old buffer
|
|
{
|
|
delete [] debug_pDebugBuffer;
|
|
debug_pDebugBuffer = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
void StartRecordingPlayerVehicle()
|
|
{
|
|
if (!debug_WidgetRecordingPlaying && FindPlayerVehicle())
|
|
{
|
|
CVehicleRecordingMgr::StartRecordingCar(FindPlayerVehicle(), CVehicleRecordingMgr::ms_debug_RecordingNumToPlayWith, CVehicleRecordingMgr::ms_debug_RecordingNameToPlayWith, true);
|
|
debug_WidgetRecordingPlaying = true;
|
|
}
|
|
}
|
|
|
|
void StartRecordingPlayerVehicleTransitionFromPlayback()
|
|
{
|
|
if (!debug_WidgetRecordingPlaying && FindPlayerVehicle())
|
|
{
|
|
CVehicleRecordingMgr::StartRecordingCarTransitionFromPlayback(FindPlayerVehicle(), CVehicleRecordingMgr::ms_debug_RecordingNumToPlayWith, CVehicleRecordingMgr::ms_debug_RecordingNameToPlayWith, true);
|
|
debug_WidgetRecordingPlaying = true;
|
|
}
|
|
}
|
|
|
|
void StopRecordingPlayerVehicle()
|
|
{
|
|
if (debug_WidgetRecordingPlaying)
|
|
{
|
|
TidyOldDebugBuffer();
|
|
|
|
CVehicleRecordingMgr::StopRecordingCar(FindPlayerVehicle(), &debug_pDebugBuffer, &debug_bufferSize);
|
|
debug_WidgetRecordingPlaying = false;
|
|
}
|
|
}
|
|
|
|
void StopRecordingPlayback()
|
|
{
|
|
if(debug_WidgetRecordingPlaybackVehicle)
|
|
{
|
|
CVehicleRecordingMgr::StopPlaybackRecordedCar(debug_WidgetRecordingPlaybackVehicle);
|
|
debug_WidgetRecordingPlaybackVehicle = NULL;
|
|
}
|
|
}
|
|
|
|
void PlaybackRecordingUsingPlayerVehicle()
|
|
{
|
|
taskAssertf(debug_pDebugBuffer, "No recording loaded, load recording first");
|
|
if (debug_pDebugBuffer)
|
|
{
|
|
StopRecordingPlayback();
|
|
debug_WidgetRecordingPlaybackVehicle = FindPlayerVehicle();
|
|
if(debug_WidgetRecordingPlaybackVehicle)
|
|
{
|
|
CVehicleRecordingMgr::StartPlaybackRecordedCar(debug_WidgetRecordingPlaybackVehicle, -1, false, 20, DMode_StopForCars, false, -1, debug_pDebugBuffer,
|
|
debug_bufferSize);
|
|
audVehicleAudioEntity::LoadCRAudioSettings();
|
|
}
|
|
}
|
|
}
|
|
|
|
void PlaybackRecordingUsingFocusVehicle()
|
|
{
|
|
if (debug_pDebugBuffer)
|
|
{
|
|
StopRecordingPlayback();
|
|
|
|
CVehicle* focusVechicle = CVehicleDebug::GetFocusVehicle();
|
|
if(focusVechicle)
|
|
{
|
|
debug_WidgetRecordingPlaybackVehicle = focusVechicle;
|
|
CVehicleRecordingMgr::StartPlaybackRecordedCar(debug_WidgetRecordingPlaybackVehicle, -1, false, 20, DMode_StopForCars, false, -1, debug_pDebugBuffer,
|
|
debug_bufferSize);
|
|
}
|
|
}
|
|
}
|
|
|
|
void PausePlayback()
|
|
{
|
|
if(debug_pDebugBuffer && debug_WidgetRecordingPlaybackVehicle)
|
|
{
|
|
CVehicleRecordingMgr::PausePlaybackRecordedCar(debug_WidgetRecordingPlaybackVehicle);
|
|
}
|
|
}
|
|
|
|
void ResumePlayback()
|
|
{
|
|
if(debug_pDebugBuffer && debug_WidgetRecordingPlaybackVehicle)
|
|
{
|
|
CVehicleRecordingMgr::UnpausePlaybackRecordedCar(debug_WidgetRecordingPlaybackVehicle);
|
|
}
|
|
}
|
|
|
|
void SaveRecording()
|
|
{
|
|
if (debug_pDebugBuffer)
|
|
{
|
|
CVehicleRecordingMgr::CRecording::SaveToFile(debug_pDebugBuffer, debug_bufferSize, CVehicleRecordingMgr::ms_debug_RecordingNumToPlayWith, CVehicleRecordingMgr::ms_debug_RecordingNameToPlayWith);
|
|
}
|
|
}
|
|
|
|
void LoadRecording()
|
|
{
|
|
TidyOldDebugBuffer();
|
|
|
|
CVehicleRecordingMgr::CRecording::LoadFromFile(&debug_pDebugBuffer, &debug_bufferSize, CVehicleRecordingMgr::ms_debug_RecordingNumToPlayWith, CVehicleRecordingMgr::ms_debug_RecordingNameToPlayWith);
|
|
}
|
|
|
|
void CVehicleRecordingMgr::CreateAudioWidgets()
|
|
{
|
|
if(ms_pCreateAudioWidgetsButton)
|
|
{
|
|
bkBank *pBank = BANKMGR.FindBank("Game Logic");
|
|
if(pBank)
|
|
{
|
|
if(audVehicleAudioEntity::AddCarRecordingWidgets(pBank))
|
|
{
|
|
ms_pCreateAudioWidgetsButton->Destroy();
|
|
ms_pCreateAudioWidgetsButton = NULL;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
void CVehicleRecordingMgr::InitWidgets()
|
|
{
|
|
bkBank *pBank = CGameLogic::GetGameLogicBank();
|
|
Assert(pBank);
|
|
|
|
if (pBank)
|
|
{
|
|
pBank->PushGroup("Car recordings", false);
|
|
// debug widgets:
|
|
pBank->AddToggle("Toggle update with physics", &CVehicleRecordingMgr::sm_bUpdateWithPhysics);
|
|
pBank->AddToggle("Toggle update before PreSimUpdate", &CVehicleRecordingMgr::sm_bUpdateBeforePreSimUpdate);
|
|
|
|
// Start test recording on player
|
|
pBank->AddSlider("Recording number used below", &ms_debug_RecordingNumToPlayWith, 0, 10000, 1);
|
|
pBank->AddText("Recording name used below", &ms_debug_RecordingNameToPlayWith[0], sizeof(ms_debug_RecordingNameToPlayWith));
|
|
|
|
// Playback test recording on player vehicle
|
|
pBank->AddButton("Start recording player vehicle", datCallback(CFA(StartRecordingPlayerVehicle)));
|
|
pBank->AddButton("Start re-recording player vehicle, transitioning from playback", datCallback(CFA(StartRecordingPlayerVehicleTransitionFromPlayback)));
|
|
pBank->AddButton("Stop recording player vehicle", datCallback(CFA(StopRecordingPlayerVehicle)));
|
|
pBank->AddButton("Playback recording using player vehicle", datCallback(CFA(PlaybackRecordingUsingPlayerVehicle)));
|
|
pBank->AddButton("Playback recording using focus vehicle", datCallback(CFA(PlaybackRecordingUsingFocusVehicle)));
|
|
pBank->AddButton("Pause playback", datCallback(CFA(PausePlayback)));
|
|
pBank->AddButton("Resume playback", datCallback(CFA(ResumePlayback)));
|
|
pBank->AddButton("Stop playback", datCallback(CFA(StopRecordingPlayback)));
|
|
pBank->AddButton("Save recording", datCallback(CFA(SaveRecording)));
|
|
pBank->AddButton("Load recording", datCallback(CFA(LoadRecording)));
|
|
|
|
pBank->AddSlider("Playback time scale", &debug_timeScale, 0.01f, 10.0f, 0.1f);
|
|
|
|
pBank->AddVector("Playback additional rotation (Eulers in Deg)", &debug_AdditionalRotationEulers, -180.0f, 180.0f, 0.1f);
|
|
pBank->AddVector("Playback local position offset", &debug_LocalPositionOffset, -10000.0f, 10000.0f, 0.1f);
|
|
pBank->AddVector("Playback global position offset", &debug_GlobalPositionOffset, -10000.0f, 10000.0f, 0.1f);
|
|
|
|
// pBank->AddToggle("Reduce car recording", &bReduceRecording);
|
|
// pBank->AddSlider("Reduction sample step", &ReductionSampleStep, 0, 696969, 1);
|
|
// pBank->AddSlider("Recording to be reduced", &RecordingToBeReduced, -1, 10000, 1);
|
|
|
|
pBank->AddToggle("Fix car recording time-stamps", &bFixRecordingTimeStamps);
|
|
pBank->AddToggle("Shift car recording", &bShiftRecording);
|
|
pBank->AddSlider("Shift X", &ShiftX, -10000.0f, 10000.0f, 1.0f);
|
|
pBank->AddSlider("Shift Y", &ShiftY, -10000.0f, 10000.0f, 1.0f);
|
|
pBank->AddSlider("Shift Z", &ShiftZ, -10000.0f, 10000.0f, 1.0f);
|
|
pBank->AddSlider("Recording number to be shifted", &RecordingNumberToBeShifted, -1, 10000, 1);
|
|
pBank->AddText("Recording name to be shifted", &RecordingNameToBeShifted[0], sizeof(RecordingNameToBeShifted));
|
|
|
|
pBank->AddToggle("Render all recordings", &bRenderAllRecordings);
|
|
|
|
//pBank->AddButton("Convert all recordings(.rrr) to ascii (.aaa)", datCallback(CFA(ConvertFilesToAscii)));
|
|
//pBank->AddButton("Convert all ascii (.aaa) to recordings(.rrr)", datCallback(CFA(ConvertFilesFromAscii)));
|
|
|
|
pBank->AddSlider("Shift recorded cars up during playback", &fShiftPlaybackUpValue, -100.0f, 100.0f, 1.0f);
|
|
pBank->AddToggle("Force recording vehicle to dummy", &CVehicle::sm_bCanForceCarRecordingToDummy);
|
|
pBank->AddToggle("Force recorded vehicle to be inactive", &CVehicle::sm_bForceRecordedVehicleToBeInactive);
|
|
pBank->AddToggle("Force recorded vehicle to be active", &CVehicle::sm_bForceRecordedVehicleToBeActive);
|
|
|
|
//Audio tool
|
|
ms_pCreateAudioWidgetsButton = pBank->AddButton("Create audio tool.", datCallback(CFA(CreateAudioWidgets)));
|
|
sm_WidgetGroup = smart_cast<bkGroup*>(pBank->GetCurrentGroup());
|
|
naAssert(sm_WidgetGroup);
|
|
pBank->PopGroup();
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// NAME : ShiftRecording()
|
|
// PURPOSE : Will load a recording. Shift the recording by a certain amount
|
|
// After that the recording is saved out again (as .rrrshifted)
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
#endif
|
|
#endif
|
|
|
|
void CVehicleRecordingMgr::RegisterStreamingModule()
|
|
{
|
|
strStreamingEngine::GetInfo().GetModuleMgr().AddModule(GetStreamingModule());
|
|
}
|
|
|
|
IMPLEMENT_PLACE(CVehicleRecording);
|
|
|