// Copyright (C) 2007 Id Software, Inc. // #include "../precompiled.h" #pragma hdrstop #if defined( _DEBUG ) && !defined( ID_REDIRECT_NEWDELETE ) #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif #include "DemoManager.h" #include "DemoScript.h" #include "../Player.h" #include "../guis/UserInterfaceLocal.h" //=============================================================== // // sdDemoProperties // //=============================================================== /* ================ sdDemoProperties::GetProperty ================ */ sdProperties::sdProperty* sdDemoProperties::GetProperty( const char* name ) { return properties.GetProperty( name, sdProperties::PT_INVALID, false ); } /* ============ sdDemoProperties::GetProperty ============ */ sdProperties::sdProperty* sdDemoProperties::GetProperty( const char* name, sdProperties::ePropertyType type ) { sdProperties::sdProperty* prop = properties.GetProperty( name, sdProperties::PT_INVALID, false ); if ( prop && prop->GetValueType() != type ) { gameLocal.Error( "sdDemoProperties::GetProperty: type mismatch for property '%s'", name ); } return prop; } /* ================ sdDemoProperties::Init ================ */ void sdDemoProperties::Init() { properties.RegisterProperty( "state", state ); properties.RegisterProperty( "time", time ); properties.RegisterProperty( "size", size ); properties.RegisterProperty( "position", position ); properties.RegisterProperty( "frame", frame ); properties.RegisterProperty( "cutIsSet", cutIsSet ); properties.RegisterProperty( "cutStartMarker", cutStartMarker ); properties.RegisterProperty( "cutEndMarker", cutEndMarker ); properties.RegisterProperty( "demoName", demoName ); properties.RegisterProperty( "writingMDF", writingMDF ); properties.RegisterProperty( "mdfName", mdfName ); properties.RegisterProperty( "viewOrigin", viewOrigin ); properties.RegisterProperty( "viewAngles", viewAngles ); } /* ================ sdDemoProperties::Shutdown ================ */ void sdDemoProperties::Shutdown() { properties.Clear(); } /* ================ sdDemoProperties::UpdateProperties ================ */ void sdDemoProperties::UpdateProperties() { state = sdDemoManager::GetInstance().GetState(); time = MS2SEC( sdDemoManager::GetInstance().GetTime() ); size = sdDemoManager::GetInstance().GetSize(); position = sdDemoManager::GetInstance().GetPosition(); frame = sdDemoManager::GetInstance().GetFrame(); cutIsSet = sdDemoManager::GetInstance().CutIsSet() ? 1.0f : 0.0f; cutStartMarker = sdDemoManager::GetInstance().GetCutStartMarker(); cutEndMarker = sdDemoManager::GetInstance().GetCutEndMarker(); demoName = networkSystem->GetDemoName(); writingMDF = sdDemoManager::GetInstance().WritingMDF() ? 1.0f : 0.0f; mdfName = sdDemoManager::GetInstance().GetMDFFileName(); const renderView_t& view = sdDemoManager::GetInstance().GetRenderedView(); viewOrigin = view.vieworg; idAngles angles = view.viewaxis.ToAngles(); viewAngles = idVec3( angles.pitch, angles.yaw, angles.roll ); } //=============================================================== // // sdDemoManagerLocal // //=============================================================== idCVar sdDemoManagerLocal::g_demoOutputMDF( "g_demoOutputMDF", "0", CVAR_GAME | CVAR_INTEGER, "output entity keyframe data from demo", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> ); idCVar sdDemoManagerLocal::g_showDemoHud( "g_showDemoHud", "0", CVAR_GAME | CVAR_BOOL, "draw the demo hud gui" ); idCVar sdDemoManagerLocal::g_showDemoView( "g_showDemoView", "0", CVAR_GAME | CVAR_BOOL, "show player's calculated view when paused instead of free-fly cam" ); idCVar sdDemoManagerLocal::g_demoAnalyze( "g_demoAnalyze", "0", CVAR_GAME | CVAR_BOOL, "analyze demo during playback" ); idCVar sdDemoManagerLocal::demo_noclip( "demo_noclip", "0", CVAR_SYSTEM | CVAR_BOOL, "noclip through a demo" ); /* ================ sdDemoManagerLocal::sdDemoManagerLocal ================ */ sdDemoManagerLocal::~sdDemoManagerLocal() { } /* ================ sdDemoManagerLocal::Init ================ */ void sdDemoManagerLocal::Init() { sdDemoScript::InitClass(); cameraFactory.RegisterType( sdDemoCamera_Fixed::GetType(), sdCameraFactory::Allocator< sdDemoCamera_Fixed > ); cameraFactory.RegisterType( sdDemoCamera_Anim::GetType(), sdCameraFactory::Allocator< sdDemoCamera_Anim > ); state = DS_NONE; script = NULL; activeCamera = NULL; memset( &renderedView, 0, sizeof( renderView_t ) ); demoGameFrames = 0; pausedTime = previousPausedTime = 0; melFrames = 0; viewAngles.Zero(); deltaViewAngles.Zero(); viewOrigin.Zero(); currentVelocity.Zero(); melDataFile = NULL; localDemoProperties.Init(); } /* ================ sdDemoManagerLocal::InitGUIs ================ */ void sdDemoManagerLocal::InitGUIs() { hud = gameLocal.LoadUserInterface( "guis/demos/hud", false, true ); } /* ============ sdDemoManagerLocal::GetPosition ============ */ float sdDemoManagerLocal::GetPosition() const { if( endPosition == startPosition ) { return 0; } return ( position - startPosition ) / static_cast< float >( endPosition - startPosition ); } /* ============ sdDemoManagerLocal::GetCutStartMarker ============ */ float sdDemoManagerLocal::GetCutStartMarker() const { if( endPosition == startPosition ) { return 0; } return ( cutStartMarker - startPosition ) / static_cast< float >( endPosition - startPosition ); } /* ============ sdDemoManagerLocal::GetCutEndMarker ============ */ float sdDemoManagerLocal::GetCutEndMarker() const { if( endPosition == startPosition ) { return 0; } return ( cutEndMarker - startPosition ) / static_cast< float >( endPosition - startPosition ); } /* ================ sdDemoManagerLocal::StartDemo ================ */ void sdDemoManagerLocal::StartDemo() { state = static_cast< demoState_t >( networkSystem->GetDemoState( time, position, length, startPosition, endPosition, cutStartMarker, cutEndMarker ) ); if ( state != DS_PLAYING ) { return; } viewOrigin.Zero(); viewAngles.Zero(); currentVelocity.Zero(); deltaViewAngles.Zero(); renderedView.vieworg.Zero(); renderedView.viewaxis.Identity(); script = new sdDemoScript; if ( !script->Parse( networkSystem->GetDemoName() ) ) { delete script; script = NULL; } activeCamera = NULL; demoGameFrames = 0; if ( g_demoOutputMDF.GetInteger() > 0 ) { melDataFileName = "demos/"; melDataFileName += networkSystem->GetDemoName(); if ( g_demoOutputMDF.GetInteger() == 2 ) { sysTime_t time; sys->RealTime( &time ); melDataFileName += va( "_%d%02d%02d_%02d%02d%02d", 1900 + time.tm_year, 1 + time.tm_mon, time.tm_mday, time.tm_hour, time.tm_min, time.tm_sec ); } melDataFileName.SetFileExtension( "mdf" ); melDataFile = fileSystem->OpenFileWrite( melDataFileName, "fs_devpath" ); melFrames = 0; if ( melDataFile ) { melDataFile->WriteInt( 0 ); // dummy for numFrames melDataFile->WriteInt( ENTITYNUM_MAX_NORMAL ); // melDataFile->WriteInt( g_demoOutputMDF.GetInteger() == 2 ? 1 : 0 ); // timestamps in demo? melDataFile->WriteInt( 1 ); // timestamps in demo? melPrimitives.AssureSize( ENTITYNUM_MAX_NORMAL, NULL ); } } if ( g_demoAnalyze.GetBool() ) { demoAnalyzer.Start(); } sdUserInterfaceLocal* ui = gameLocal.GetUserInterface( hud ); if ( ui && !ui->IsActive() ) { ui->Activate(); } localDemoProperties.UpdateProperties(); } /* ================ sdDemoManagerLocal::EndDemo ================ */ void sdDemoManagerLocal::EndDemo() { state = DS_NONE; delete script; script = NULL; melDataFileName.Clear(); if ( melDataFile ) { melDataFile->Seek( 0, FS_SEEK_SET ); melDataFile->WriteInt( melFrames ); fileSystem->CloseFile( melDataFile ); melDataFile = NULL; melPrimitives.DeleteContents( true ); } if ( demoAnalyzer.IsActive() ) { demoAnalyzer.Stop(); } sdUserInterfaceLocal* ui = gameLocal.GetUserInterface( hud ); if ( ui && ui->IsActive() ) { ui->Deactivate(); } } /* ================ sdDemoManagerLocal::RunDemoFrame ================ */ void sdDemoManagerLocal::RunDemoFrame( const usercmd_t* demoCmd ) { if ( demoCmd ) { this->demoCmd = *demoCmd; } else { memset( &( this->demoCmd ), 0, sizeof( usercmd_t ) ); if ( gameLocal.localClientNum >= 0 && gameLocal.localClientNum < MAX_CLIENTS ) { for( int i = 0; i < 3; i++ ) { this->demoCmd.angles[ i ] = gameLocal.usercmds[ gameLocal.localClientNum ].angles[ i ]; } } } // update demo state demoState_t oldState = state; previousTime = time; state = static_cast< demoState_t >( networkSystem->GetDemoState( time, position, length, startPosition, endPosition, cutStartMarker, cutEndMarker ) ); switch( state ) { case DS_PAUSED: if ( oldState != DS_PAUSED ) { if ( !demo_noclip.GetBool() ) { idPlayer* player = gameLocal.GetLocalViewPlayer(); if ( player != NULL ) { viewOrigin = player->firstPersonViewOrigin; viewAngles = player->viewAngles; } currentVelocity.Zero(); } for( int i = 0; i < 3; i++ ) { deltaViewAngles[ i ] = viewAngles[ i ] - SHORT2ANGLE( this->demoCmd.angles[ i ] ); } pausedTime = sys->Milliseconds(); } previousPausedTime = pausedTime; pausedTime = sys->Milliseconds(); break; case DS_PLAYING: if ( script ) { script->RunFrame(); } if ( activeCamera ) { activeCamera->RunFrame(); } if ( demoAnalyzer.IsActive() ) { demoAnalyzer.RunFrame(); } break; } localDemoProperties.UpdateProperties(); } /* ================ sdDemoManagerLocal::EndDemoFrame ================ */ void sdDemoManagerLocal::EndDemoFrame() { if ( state == DS_PLAYING && melDataFile && gameLocal.isNewFrame ) { const sdDeclTargetInfo* targetInfoDecl = gameLocal.GetMDFExportTargets(); idList< idEntity* > filteredEntities; idList< idEntity* > entityList; idList< int > entityNumList; // if ( g_demoOutputMDF.GetInteger() == 2 ) { // write timestamp, assumes demo is played back in normal speed (33 msec frames) melDataFile->WriteInt( ( gameLocal.time - gameLocal.startTime ) / USERCMD_MSEC ); // } // // write camera data // idVec3 cameraOrigin; idMat3 cameraAxis; idAngles cameraAngles; cameraOrigin = renderedView.vieworg; cameraAxis[ 0 ] = -renderedView.viewaxis[ 1 ]; cameraAxis[ 1 ] = renderedView.viewaxis[ 2 ]; cameraAxis[ 2 ] = -renderedView.viewaxis[ 0 ]; cameraOrigin.ToMayaSelf(); cameraAxis.ToMayaSelf(); cameraAngles = cameraAxis.ToAnglesMaya(); // origin melDataFile->WriteDouble( cameraOrigin.x ); melDataFile->WriteDouble( cameraOrigin.y ); melDataFile->WriteDouble( cameraOrigin.z ); // angles melDataFile->WriteDouble( cameraAngles.pitch ); melDataFile->WriteDouble( cameraAngles.yaw ); melDataFile->WriteDouble( cameraAngles.roll ); // // filter entities and make a list of entities to hide // for ( int i = 0; i < ENTITYNUM_MAX_NORMAL; i++ ) { idEntity* ent = gameLocal.entities[ i ]; if ( !ent ) { if ( melPrimitives[ i ] && melPrimitives[ i ]->visible ) { entityNumList.Append( i ); } continue; } if ( targetInfoDecl ) { if ( !targetInfoDecl->FilterEntity( ent ) ) { if ( melPrimitives[ ent->entityNumber ] && melPrimitives[ ent->entityNumber ]->visible ) { entityNumList.Append( ent->entityNumber ); } continue; } } filteredEntities.Append( ent ); } // write list melDataFile->WriteInt( entityNumList.Num() ); for ( int i = 0; i < entityNumList.Num(); i++ ) { melDataFile->WriteInt( entityNumList[ i ] ); } // // build list of entities to create // entityList.Clear(); for ( int i = 0; i < filteredEntities.Num(); i++ ) { idEntity* ent = filteredEntities[ i ]; if ( !melPrimitives[ ent->entityNumber ] ) { melPrimitives[ ent->entityNumber ] = new melPrimitive_t; melPrimitives[ ent->entityNumber ]->visible = false; melPrimitives[ ent->entityNumber ]->bounds.Zero(); entityList.Append( ent ); } } // write list melDataFile->WriteInt( entityList.Num() ); for ( int i = 0; i < entityList.Num(); i++ ) { melDataFile->WriteInt( entityList[ i ]->entityNumber ); } // // build list of entities to make visible // entityList.Clear(); for ( int i = 0; i < filteredEntities.Num(); i++ ) { idEntity* ent = filteredEntities[ i ]; if ( !melPrimitives[ ent->entityNumber ]->visible ) { melPrimitives[ ent->entityNumber ]->visible = true; entityList.Append( ent ); } } // write list melDataFile->WriteInt( entityList.Num() ); for ( int i = 0; i < entityList.Num(); i++ ) { melDataFile->WriteInt( entityList[ i ]->entityNumber ); } // // build list of entity bounding box updates // entityList.Clear(); for ( int i = 0; i < filteredEntities.Num(); i++ ) { idEntity* ent = filteredEntities[ i ]; if ( !melPrimitives[ ent->entityNumber ]->bounds.Compare( ent->GetPhysics()->GetBounds() ) && !ent->GetPhysics()->GetBounds().IsCleared() ) { melPrimitives[ ent->entityNumber ]->bounds = ent->GetPhysics()->GetBounds(); //if ( !melPrimitives[ ent->entityNumber ]->bounds.Compare( ent->GetRenderEntity()->bounds ) ) { // melPrimitives[ ent->entityNumber ]->bounds = ent->GetRenderEntity()->bounds; entityList.Append( ent ); } } // write list melDataFile->WriteInt( entityList.Num() ); for ( int i = 0; i < entityList.Num(); i++ ) { idEntity* ent = entityList[ i ]; melDataFile->WriteInt( ent->entityNumber ); // size information melDataFile->WriteDouble( melPrimitives[ ent->entityNumber ]->bounds.GetMaxs().x - melPrimitives[ ent->entityNumber ]->bounds.GetMins().x ); melDataFile->WriteDouble( melPrimitives[ ent->entityNumber ]->bounds.GetMaxs().y - melPrimitives[ ent->entityNumber ]->bounds.GetMins().y ); melDataFile->WriteDouble( melPrimitives[ ent->entityNumber ]->bounds.GetMaxs().z - melPrimitives[ ent->entityNumber ]->bounds.GetMins().z ); } // // write list of entity origin and angles // melDataFile->WriteInt( filteredEntities.Num() ); for ( int i = 0; i < filteredEntities.Num(); i++ ) { idEntity* ent = filteredEntities[ i ]; melDataFile->WriteInt( ent->entityNumber ); // origin idVec3 originMaya = (ent->GetPhysics()->GetOrigin()).ToMaya(); //idVec3 originMaya = (ent->GetRenderEntity()->origin).ToMaya(); melDataFile->WriteDouble( originMaya.x ); melDataFile->WriteDouble( originMaya.y ); melDataFile->WriteDouble( originMaya.z ); // angles if ( ent->IsType( idPlayer::Type ) ) { melDataFile->WriteDouble( ent->GetPhysics()->GetAxis().ToAngles().roll ); melDataFile->WriteDouble( static_cast(ent)->viewAngles.yaw - 90.f ); melDataFile->WriteDouble( -ent->GetPhysics()->GetAxis().ToAngles().pitch ); } else { idMat3 mayaAxis; idAngles anglesMaya; mayaAxis[ 0 ] = -ent->GetPhysics()->GetAxis()[ 1 ]; mayaAxis[ 1 ] = ent->GetPhysics()->GetAxis()[ 2 ]; mayaAxis[ 2 ] = -ent->GetPhysics()->GetAxis()[ 0 ]; mayaAxis.ToMayaSelf(); anglesMaya = mayaAxis.ToAnglesMaya(); melDataFile->WriteDouble( anglesMaya.pitch ); melDataFile->WriteDouble( anglesMaya.yaw ); melDataFile->WriteDouble( anglesMaya.roll ); } } melFrames++; } if ( gameLocal.isNewFrame && state != DS_PAUSED ) { demoGameFrames++; } } /* ================ sdDemoManagerLocal::SetActiveCamera ================ */ void sdDemoManagerLocal::SetActiveCamera( sdDemoCamera* camera ) { activeCamera = camera; activeCamera->Start(); // jrad - ensure that this is running updates sdPostProcess* postProcess = gameLocal.localPlayerProperties.GetPostProcess(); if( postProcess != NULL && !postProcess->Enabled() ) { postProcess->Enable( true ); } } /* ================ sdDemoManagerLocal::CalculateRenderView ================ */ bool sdDemoManagerLocal::CalculateRenderView( renderView_t* renderView ) { if ( sdDemoManager::GetInstance().NoClip() ) { // jrad - ensure that this is running updates sdPostProcess* postProcess = gameLocal.localPlayerProperties.GetPostProcess(); if( postProcess != NULL && !postProcess->Enabled() ) { postProcess->Enable( true ); } // update camera origin for( int i = 0; i < 3; i++ ) { viewAngles[ i ] = idMath::AngleNormalize180( SHORT2ANGLE( demoCmd.angles[ i ] ) + deltaViewAngles[ i ] ); } viewAngles.pitch = idMath::ClampFloat( -89.f, 89.f, viewAngles.pitch ); for( int i = 0; i < 3; i++ ) { deltaViewAngles[ i ] = viewAngles[ i ] - SHORT2ANGLE( demoCmd.angles[ i ] ); } // calculate vectors idVec3 gravityNormal = gameLocal.GetGravity(); idVec3 viewForward, viewRight; gravityNormal.Normalize(); viewForward = viewAngles.ToForward(); viewForward.Normalize(); viewRight = gravityNormal.Cross( viewForward ); viewRight.Normalize(); // scale user command int max; float scale; int forwardmove; int rightmove; int upmove; forwardmove = demoCmd.forwardmove; rightmove = demoCmd.rightmove; upmove = demoCmd.upmove; max = abs( forwardmove ); if ( abs( rightmove ) > max ) { max = abs( rightmove ); } if ( abs( upmove ) > max ) { max = abs( upmove ); } if ( !max ) { scale = 0.0f; } else { float total = idMath::Sqrt( (float) forwardmove * forwardmove + rightmove * rightmove + upmove * upmove ); scale = pm_democamspeed.GetFloat() * max / ( 127.0f * total ); } // move float frametime; if ( state == DS_PAUSED ) { frametime = MS2SEC( pausedTime - previousPausedTime ); } else { frametime = MS2SEC( gameLocal.msec ); } float speed, drop, friction, newspeed, stopspeed; float wishspeed; idVec3 wishdir; // friction speed = currentVelocity.Length(); if ( speed < 20.f ) { currentVelocity.Zero(); } else { stopspeed = pm_democamspeed.GetFloat() * 0.3f; if ( speed < stopspeed ) { speed = stopspeed; } friction = 12.0f /*PM_NOCLIPFRICTION*/; drop = speed * friction * frametime; // scale the velocity newspeed = speed - drop; if (newspeed < 0) { newspeed = 0; } currentVelocity *= newspeed / speed; } // accelerate wishdir = scale * (viewForward * demoCmd.forwardmove + viewRight * demoCmd.rightmove); wishdir -= scale * gravityNormal * demoCmd.upmove; wishspeed = wishdir.Normalize(); wishspeed *= scale; // q2 style accelerate float addspeed, accelspeed, currentspeed; currentspeed = currentVelocity * wishdir; addspeed = wishspeed - currentspeed; if ( addspeed > 0 ) { accelspeed = 10.0f /*PM_ACCELERATE*/ * frametime * wishspeed; if ( accelspeed > addspeed ) { accelspeed = addspeed; } currentVelocity += accelspeed * wishdir; } // move viewOrigin += frametime * currentVelocity; renderView->viewID = 0; renderView->vieworg = viewOrigin; renderView->viewaxis = viewAngles.ToMat3(); renderView->fov_x = g_fov.GetFloat(); renderView->time = gameLocal.time; renderView->x = 0; renderView->y = 0; renderView->width = SCREEN_WIDTH; renderView->height = SCREEN_HEIGHT; return true; } else { currentVelocity.Zero(); } if ( state == DS_PLAYING ) { if ( activeCamera ) { renderView->viewID = 0; renderView->vieworg = activeCamera->GetOrigin(); renderView->viewaxis = activeCamera->GetAxis(); renderView->fov_x = activeCamera->GetFov(); renderView->time = gameLocal.time; renderView->x = 0; renderView->y = 0; renderView->width = SCREEN_WIDTH; renderView->height = SCREEN_HEIGHT; return true; } } return false; } /* ================ sdDemoManagerLocal::Shutdown ================ */ void sdDemoManagerLocal::Shutdown() { localDemoProperties.Shutdown(); cameraFactory.Shutdown(); delete script; sdDemoScript::Shutdown(); }