// VehicleDriver.cpp // // Copyright 2002-2004 Raven Software //---------------------------------------------------------------- #include "../../idlib/precompiled.h" #pragma hdrstop #include "../Game_local.h" #include const idEventDef VD_ChoosePathTarget( "choosePathTarget", "e", 'e' ); const idEventDef EV_FollowOffset( "followOffset", "v" ); const idEventDef EV_FireWeapon ( "fireWeapon", "ff" ); CLASS_DECLARATION( idActor, rvVehicleDriver ) EVENT( EV_PostSpawn , rvVehicleDriver::Event_PostSpawn ) EVENT( AI_EnterVehicle , rvVehicleDriver::Event_EnterVehicle ) EVENT( AI_ExitVehicle , rvVehicleDriver::Event_ExitVehicle ) EVENT( AI_ScriptedMove , rvVehicleDriver::Event_ScriptedMove ) EVENT( AI_ScriptedDone , rvVehicleDriver::Event_ScriptedDone ) EVENT( AI_ScriptedStop , rvVehicleDriver::Event_ScriptedStop ) EVENT( EV_Activate , rvVehicleDriver::Event_Trigger ) EVENT( EV_Speed , rvVehicleDriver::Event_SetSpeed ) EVENT( EV_FireWeapon , rvVehicleDriver::Event_FireWeapon ) EVENT( AI_FaceEntity , rvVehicleDriver::Event_FaceEntity ) EVENT( AI_LookAt , rvVehicleDriver::Event_LookAt ) EVENT( AI_SetLeader , rvVehicleDriver::Event_SetLeader ) //twhitaker: remove - begin EVENT( EV_FollowOffset , rvVehicleDriver::Event_SetFollowOffset ) //twhitaker: remove - end END_CLASS /* ================ rvVehicleDriver::rvVehicleDriver ================ */ rvVehicleDriver::rvVehicleDriver ( void ) { } /* ================ rvVehicleDriver::Spawn ================ */ void rvVehicleDriver::Spawn ( void ) { currentThrottle = 1.0f; faceTarget = NULL; lookTarget = NULL; leader = NULL; leaderFlags = 0; decelDistance = 0.0f; minDistance = 0.0f; fireEndTime = 0.0f; isMoving = false; avoidingLeader = false; pathingMode = VDPM_Random; pathingOrigin = vec3_origin; pathingEntity = NULL; SIMDProcessor->Memset( &pathTargetInfo, 0, sizeof( PathTargetInfo ) ); SIMDProcessor->Memset( &lastPathTargetInfo, 0, sizeof( PathTargetInfo ) ); PostEventMS( &EV_PostSpawn, 0 ); } /* ================ rvVehicleDriver::Think ================ */ void rvVehicleDriver::Think ( void ) { if ( !IsDriving() ) { if ( leader ) { leader->SetLeaderHint( VDLH_Continue ); leader = NULL; } return; } if ( pathTargetInfo.node ) { float targetDistance = 0.0f; float dotForward = 0.0f; float dotRight = 0.0f; float desiredThrottle = 1.0f; idEntity* currentPathTarget = pathTargetInfo.node; rvVehicle* vehicle = vehicleController.GetVehicle(); idMat3 vehicleAxis = vehicle->GetAxis(); idVec3 targetOrigin; idVec3 dirToTarget; usercmd_t cmd; idAngles ang; // We may want to hack the auto correction variable based on what the state of the driver and the driver's leader. UpdateAutoCorrection(); if ( vehicle->IsAutoCorrecting( ) ) { if ( lastPathTargetInfo.node ) { currentPathTarget = lastPathTargetInfo.node; } if ( g_debugVehicleDriver.GetInteger() != 0 ) { gameRenderWorld->DebugBounds( colorCyan, vehicle->GetPhysics()->GetAbsBounds().Expand( 30 ) ); } } // Touch all the triggers that the vehicle touches vehicle->TouchTriggers(); GetTargetInfo( currentPathTarget, &targetOrigin, &dirToTarget, &dotForward, &dotRight, &targetDistance ); // The primary purpose of the following portions of code is to set the desiredThrottle variable. if ( IsMoving() ) { // if we're slowing down lerp throttle. if ( pathTargetInfo.throttle < lastPathTargetInfo.throttle ) { desiredThrottle = idMath::Lerp( lastPathTargetInfo.throttle, pathTargetInfo.throttle, targetDistance / pathTargetInfo.initialDistance ); } else { // otherwise accelerate rapidly or maintain throttle. desiredThrottle = pathTargetInfo.throttle; } // we could potentially be a leader, so check the leader flags if ( leaderFlags & VDLH_SlowDown ) { desiredThrottle = 0.1f; } else if ( leaderFlags & VDLH_Wait ) { desiredThrottle = 0.0f; } // if we're following a someone ... if ( leader ) { bool canSeeLeader = vehicle->CanSee( leader->vehicleController.GetVehicle(), false ); float distanceToLeader = ( leader->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ).Length(); avoidingLeader = false; // update the leaders flags based on distance and visibility if ( !canSeeLeader ) { // if we can't see the leader, tell him to stop leader->SetLeaderHint( VDLH_Wait ); } else if ( distanceToLeader > decelDistance ) { // if we're too far from the leader, tell him to slow down leader->SetLeaderHint( VDLH_SlowDown ); } else { // we're within range of the leader so let him move along at a normal rate leader->SetLeaderHint( VDLH_Continue ); // if we're too close to the leader, we need to slow ourself down if ( distanceToLeader < minDistance ) { desiredThrottle = -0.2f; avoidingLeader = true; } } } // the desiredThrottle variable should be set at this point, the only other thing that could change it // would be in SimulateKeys, if the dotForward < 0. } vehicleController.GetInput( cmd, ang ); // Simulate Input SimulateKeys( cmd, dotForward, dotRight, desiredThrottle * currentThrottle, targetDistance ); SimulateMouseMove( cmd ); SimulateButtons( cmd ); vehicleController.SetInput( cmd, ang ); // Node Transition if( (!vehicle->IsAutoCorrecting() || !IsValidPathNode( currentPathTarget )) && isMoving ) { idVec3 point = targetOrigin - dirToTarget * pathTargetInfo.minDistance; if( vehicle->GetPhysics()->GetAbsBounds().ContainsPoint( point ) ) { int numTargets = NumValidTargets( currentPathTarget ); lastPathTargetInfo = pathTargetInfo; //TODO: ponder - should I be setting lastPathTargetInfo.node to pathTargetInfo.node or currentPathTarget??? if( numTargets ) { Event_ScriptedMove( ChooseNextNode( currentPathTarget ), 0, 0 ); } if( lastPathTargetInfo.exitVehicle ) { Event_ExitVehicle( true ); } else if( lastPathTargetInfo.throttle == 0 || !numTargets ) { Event_ScriptedStop(); if( !numTargets ) { pathTargetInfo.node = NULL; } } // Debug Output if ( g_debugVehicleDriver.GetInteger( ) != 0 ) { gameRenderWorld->DebugBounds( colorRed, idBounds(idVec3(-5, -5, -5), idVec3(5, 5, 5)), point ); } } } } else { // no path float dotForward; float dotRight; usercmd_t cmd; idAngles ang; vehicleController.GetInput( cmd, ang ); if ( GetTargetInfo( faceTarget, NULL, NULL, &dotForward, &dotRight, NULL) ) { if( ( 1.0f - dotForward ) < VECTOR_EPSILON ) { Event_ScriptedStop(); } else { SimulateKeys( cmd, dotForward, dotRight, 0.0f ); } } SimulateMouseMove( cmd ); SimulateButtons( cmd ); vehicleController.SetInput( cmd, ang ); } } /* ================ rvVehicleDriver::GetTargetInfo ================ */ bool rvVehicleDriver::GetTargetInfo( const idEntity* target, idVec3* targetOrigin, idVec3* dirToTarget, float* dotForward, float* dotRight, float* distance ) const { if( !target || !IsDriving() ) { return false; } idMat3 vehicleAxis = vehicleController.GetVehicle()->GetAxis(); idVec3 vehicleOrigin = vehicleController.GetVehicle()->GetOrigin(); idVec3 forward = vehicleAxis[ 0 ]; idVec3 localTargetOrigin = target->GetPhysics()->GetOrigin(); localTargetOrigin.z = vehicleOrigin.z;// Find way to include vehicleAxis here idVec3 vectorToTarget = localTargetOrigin - vehicleOrigin; idVec3 localDirToTarget = vectorToTarget; float distToTarget = localDirToTarget.Normalize(); if( targetOrigin ) { *targetOrigin = localTargetOrigin; } if( dirToTarget ) { *dirToTarget = localDirToTarget; } if( distance ) { *distance = distToTarget; } if( dotForward ) { *dotForward = localDirToTarget * forward; } if( dotRight ) { *dotRight = localDirToTarget * vehicleAxis[ 1 ]; } // Debug help if ( g_debugVehicleDriver.GetInteger( ) != 0 ) { idStr temp; const idVec3 & origin = GetPhysics()->GetOrigin(); gameRenderWorld->DebugLine( colorBlue, origin, target->GetPhysics()->GetOrigin() ); gameRenderWorld->DebugBounds( colorBlue, GetPhysics()->GetAbsBounds() ); gameRenderWorld->DebugBounds( colorBlue, target->GetPhysics()->GetBounds(), target->GetPhysics()->GetOrigin() ); gameRenderWorld->DrawText( target->GetName(), vehicleOrigin + vectorToTarget + vehicleAxis[2] * 5.0f, 1.0f, colorBlue, gameLocal.GetLocalPlayer()->viewAxis ); gameRenderWorld->DrawText( idStr( "State: " ) + LeaderHintsString( leaderFlags, temp ), origin, 1.0f, colorBlue, gameLocal.GetLocalPlayer()->viewAxis ); if ( vehicleController.GetVehicle()->IsAutoCorrecting() ) { gameRenderWorld->DebugBounds( colorPurple, vehicleController.GetVehicle()->GetPhysics()->GetAbsBounds() ); gameRenderWorld->DrawText( "Auto-Correcting", vehicleOrigin, 1.0f, colorPurple, gameLocal.GetLocalPlayer()->viewAxis ); } if ( pathTargetInfo.node && g_debugVehicleDriver.GetInteger( ) == 2 ) { gameRenderWorld->DebugBounds( colorOrange, pathTargetInfo.node->GetPhysics()->GetBounds() ); for ( int ix = 0; ix < pathTargetInfo.node->targets.Num(); ix++ ) { gameRenderWorld->DebugLine( colorOrange, pathTargetInfo.node->GetPhysics()->GetOrigin(), pathTargetInfo.node->targets[ ix ]->GetPhysics()->GetOrigin(), 0, true ); } } if ( leader ) { idVec4 & color = colorGreen; if ( leader->GetLeaderHint() & VDLH_SlowDown ) { color = colorYellow; } else if ( leader->GetLeaderHint() & VDLH_Wait ) { color = colorRed; } idStr str = idStr( "decel_distance: " ) + idStr( decelDistance ) + idStr( "\nmin_distance: " ) + idStr( minDistance ); gameRenderWorld->DrawText( str, leader->GetPhysics()->GetOrigin(), 1.0f, color, gameLocal.GetLocalPlayer()->viewAxis, 1, 0, true ); gameRenderWorld->DebugLine( color, GetPhysics()->GetOrigin(), leader->GetPhysics()->GetOrigin(), 0, true ); gameRenderWorld->DebugBounds( color, leader->vehicleController.GetVehicle()->GetPhysics()->GetAbsBounds(), vec3_origin, 0, true ); gameRenderWorld->DebugCircle( color, leader->GetPhysics()->GetOrigin(), idVec3( 0, 0, 1 ), decelDistance, 10, 0, true ); gameRenderWorld->DebugCircle( color, leader->GetPhysics()->GetOrigin(), idVec3( 0, 0, 1 ), minDistance, 10, 0, true ); } } return true; } /* ================ rvVehicleDriver::ChooseNextNode ================ */ idEntity* rvVehicleDriver::ChooseNextNode( idEntity* target ) { if( !target ) { return NULL; } if( func.Init( target->spawnArgs.GetString( "call_nextNode" )) <= 0 ) { idEntity * best = NULL; float bestDist; switch ( pathingMode ) { case VDPM_MoveTo: bestDist = FLT_MAX; for ( int i = target->targets.Num() - 1; i; i -- ) { float distance = ( target->targets[ i ]->GetPhysics()->GetOrigin() - pathingOrigin ).LengthSqr(); if ( bestDist > distance ) { bestDist = distance; best = target->targets[ i ]; } } break; case VDPM_MoveAway: bestDist = FLT_MIN; for ( int i = target->targets.Num() - 1; i; i -- ) { float distance = ( target->targets[ i ]->GetPhysics()->GetOrigin() - pathingOrigin ).LengthSqr(); if ( bestDist < distance ) { bestDist = distance; best = target->targets[ i ]; } } case VDPM_Custom: if ( pathingEntity ) { // best = pathingCallback( target ); pathingEntity->ProcessEvent( &VD_ChoosePathTarget, target ); best = gameLocal.program.GetReturnedEntity(); } } return ( best ) ? best : RandomValidTarget( target ); } func.InsertEntity( this, 0 ); func.CallFunc( &spawnArgs ); func.RemoveIndex( 0 ); return ( func.ReturnsAVal()) ? gameLocal.FindEntity( spawnArgs.GetString( func.GetReturnKey() )) : const_cast( target ); } /* ================ rvVehicleDriver::SimulateButtons ================ */ void rvVehicleDriver::SimulateButtons( usercmd_t& cmd ) { cmd.buttons = (fireEndTime >= gameLocal.time) ? BUTTON_ATTACK : 0; } /* ================ rvVehicleDriver::SimulateMouseMove ================ */ void rvVehicleDriver::SimulateMouseMove( usercmd_t& cmd ) { idVec3 origin; idMat3 axis; idVec3 vectorToTarget; idAngles anglesToTarget; idAngles turretAngles; idAngles deltaAngles; if( !lookTarget ) { for( int ix = 0; ix < 3; ++ix ) { cmd.angles[ix] = 0; } return; } vehicleController.GetEyePosition( origin, axis ); vectorToTarget = (lookTarget->GetPhysics()->GetOrigin() - origin).ToNormal(); anglesToTarget = vectorToTarget.ToAngles().Normalize360(); turretAngles = (axis[0]).ToAngles().Normalize360(); deltaAngles = (anglesToTarget - turretAngles).Normalize180(); for( int ix = 0; ix < deltaAngles.GetDimension(); ++ix ) { cmd.angles[ix] += ANGLE2SHORT( deltaAngles[ix] ); } // Debug Output if( g_debugVehicleDriver.GetInteger( ) != 0 ) { gameRenderWorld->DebugLine( colorGreen, origin, origin + anglesToTarget.ToForward() * 100.0f, 17, true ); gameRenderWorld->DebugLine( colorYellow, origin, origin + turretAngles.ToForward() * 100.0f, 17, true ); } } /* ================ rvVehicleDriver::SimulateKeys ================ */ void rvVehicleDriver::SimulateKeys( usercmd_t& cmd, float dotForward, float dotRight, float speed, float distance ) { rvVehicle * vehicle = vehicleController.GetVehicle(); if( !vehicle ) { cmd.forwardmove = 0; cmd.rightmove = 0; return; } cmd.forwardmove = static_cast< signed char >( (!vehicle->IsAutoCorrecting() ? 127.0f : forwardThrustScale) * dotForward * speed ); cmd.rightmove = static_cast< signed char >( ( ( dotForward < 0.0f ) ? rightThrustScale : -rightThrustScale ) * dotRight ); } /* ================ rvVehicleDriver::SortValid ================ */ int rvVehicleDriver::SortValid( const void* a, const void* b ) { idEntityPtr A = *(idEntityPtr*)a; idEntityPtr B = *(idEntityPtr*)b; return rvVehicleDriver::IsValidTarget( B ) - rvVehicleDriver::IsValidTarget( A ); } /* ================ rvVehicleDriver::SortTargetList ================ */ int rvVehicleDriver::SortTargetList( idEntity* ent ) const { if( !ent ) { return 0; } int numValidEntities = 0; idEntity * target; ent->RemoveNullTargets(); qsort( ent->targets.Ptr(), NumTargets( ent ), ent->targets.TypeSize(), rvVehicleDriver::SortValid ); for( int ix = NumTargets( ent ) - 1; ix >= 0; --ix ) { target = GetTarget( ent, ix ); if( IsValidTarget( target ) ) { ++numValidEntities; } } return numValidEntities; } /* ================ rvVehicleDriver::RandomValidTarget ================ */ idEntity* rvVehicleDriver::RandomValidTarget( idEntity* ent ) const { int numValid = NumValidTargets( ent ); return (!numValid) ? NULL : ent->targets[ rvRandom::irand(0, numValid - 1) ]; } /* ================ rvVehicleDriver::NumValidTargets ================ */ int rvVehicleDriver::NumValidTargets( idEntity* ent ) const { return SortTargetList(ent); } /* ================ rvVehicleDriver::NumTargets ================ */ int rvVehicleDriver::NumTargets( const idEntity* ent ) const { return (ent) ? ent->targets.Num() : 0; } /* ================ rvVehicleDriver::GetTarget ================ */ idEntity* rvVehicleDriver::GetTarget( const idEntity* ent, int index ) const { return (ent) ? ent->targets[index] : NULL; } /* ================ rvVehicleDriver::IsValidPathNode ================ */ bool rvVehicleDriver::IsValidPathNode( const idEntity* ent ) { if( !ent ) { return false; } return ent->IsType( idTarget::GetClassType() ); } /* ================ rvVehicleDriver::IsValidTarget ================ */ bool rvVehicleDriver::IsValidTarget( const idEntity* ent ) { return IsValidPathNode( ent ) || ent->IsType( rvVehicle::GetClassType() ) || ent->IsType( idPlayer::GetClassType() ); } /* ================ rvVehicleDriver::SetLeader ================ */ bool rvVehicleDriver::SetLeader( idEntity* ent ) { if ( !ent || !ent->IsType( rvVehicleDriver::GetClassType() ) ) { return false; } leader = static_cast( ent ); minDistance = leader->spawnArgs.GetFloat( "min_distance", "500" ); idStr decel_distance( minDistance * 3.0f ); decelDistance = leader->spawnArgs.GetFloat( "decel_distance", decel_distance ); return true; } /* ================ rvVehicleDriver::Event_PostSpawn ================ */ void rvVehicleDriver::Event_PostSpawn ( void ) { for ( int i = targets.Num() - 1; i >= 0; i-- ) { idEntity * ent = targets[ i ].GetEntity(); if ( ent->IsType( rvVehicle::GetClassType() ) ) { Event_EnterVehicle( ent ); } } for ( int i = targets.Num() - 1; i >= 0; i-- ) { idEntity * ent = targets[ i ].GetEntity(); if ( ent->IsType( idTarget::GetClassType() ) ) { Event_ScriptedMove( ent, 0, 0 ); } if ( ent->IsType( rvVehicleDriver::GetClassType() ) ) { SetLeader( ent ); } } } /* ================ rvVehicleDriver::Event_EnterVehicle ================ */ void rvVehicleDriver::Event_EnterVehicle ( idEntity * vehicle ) { if ( vehicle ) { forwardThrustScale = vehicle->spawnArgs.GetFloat( "driver_forward_thrust", ".75" ) * 127.0f; rightThrustScale = vehicle->spawnArgs.GetFloat( "driver_right_thrust", "1" ) * 127.0f; EnterVehicle( vehicle ); } } /* ================ rvVehicleDriver::Event_ExitVehicle ================ */ void rvVehicleDriver::Event_ExitVehicle( bool force ) { Event_ScriptedStop(); if( vehicleController.GetVehicle() && force && !ExitVehicle(force) ) { vehicleController.GetVehicle()->RemoveDriver( vehicleController.GetPosition(), force ); } pathTargetInfo.node = NULL; faceTarget = NULL; lookTarget = NULL; leader = NULL; } /* ================ rvVehicleDriver::Event_ScriptedMove ================ */ void rvVehicleDriver::Event_ScriptedMove( idEntity *target, float minDist, bool exitVehicle ) { isMoving = false; if( !target ) { return; } if( IsValidTarget( target ) ) { isMoving = true; faceTarget = NULL; pathTargetInfo.node = target; pathTargetInfo.initialDistance = ( target->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ).Length(); if ( !target->IsType( idPlayer::GetClassType() ) ) { pathTargetInfo.minDistance = target->spawnArgs.GetFloat( "min_distance", va("%f", Max(200.0f, minDist)) ); pathTargetInfo.throttle = idMath::ClampFloat( 0.0f, 1.0f, target->spawnArgs.GetFloat( "throttle", "1" ) ); pathTargetInfo.exitVehicle = target->spawnArgs.GetBool( "exit_vehicle", exitVehicle ? "1" : "0" ); if( pathTargetInfo.exitVehicle ) { pathTargetInfo.throttle = 0.0f; } } else { pathTargetInfo.minDistance = minDist; pathTargetInfo.throttle = 1.0f; pathTargetInfo.exitVehicle = false; } } else { SIMDProcessor->Memset( &pathTargetInfo, 0, sizeof( PathTargetInfo ) ); } } /* ================ rvVehicleDriver::Event_ScriptedDone ================ */ void rvVehicleDriver::Event_ScriptedDone( void ) { idThread::ReturnFloat( !IsMoving() ); } /* ================ rvVehicleDriver::Event_ScriptedStop ================ */ void rvVehicleDriver::Event_ScriptedStop( void ) { if( IsDriving() ) { usercmd_t cmd = { 0 }; vehicleController.SetInput( cmd, ang_zero ); if( pathTargetInfo.node ) { if( func.Init( pathTargetInfo.node->spawnArgs.GetString( "call_doneMoving" )) > 0 ) { func.InsertEntity( this, 0 ); func.CallFunc( &spawnArgs ); func.RemoveIndex( 0 ); } pathTargetInfo.node->ActivateTargets(this); pathTargetInfo.node = NULL; } isMoving = false; } } /* ================ rvVehicleDriver::Event_Trigger ================ */ void rvVehicleDriver::Event_Trigger( idEntity *activator ) { if( IsDriving() && pathTargetInfo.node ) { isMoving = true; } } /* ================ rvVehicleDriver::Event_SetSpeed ================ */ void rvVehicleDriver::Event_SetSpeed( float speed ) { currentThrottle = speed; } //twhitaker: remove - begin /* ================ rvVehicleDriver::Event_SetFollowOffset ================ */ void rvVehicleDriver::Event_SetFollowOffset( const idVec3 &offset ) { gameLocal.Warning( "Script Event \"followOffset\" is deprecated, please remove it immediately to avoid errors." ); } //twhitaker: remove - end /* ================ rvVehicleDriver::Event_FireWeapon ================ */ void rvVehicleDriver::Event_FireWeapon( float weapon_index, float time ) { if( IsDriving() ) { fireEndTime = gameLocal.GetTime() + SEC2MS( time ); vehicleController.SelectWeapon( weapon_index ); } } /* ================ rvVehicleDriver::Event_FaceEntity ================ */ void rvVehicleDriver::Event_FaceEntity( const idEntity* entity ) { if( IsMoving() ) { return; } faceTarget = entity; isMoving = true; } /* ================ rvVehicleDriver::Event_LookAt ================ */ void rvVehicleDriver::Event_LookAt( const idEntity* entity ) { lookTarget = entity; } /* ================ rvVehicleDriver::Event_SetLeader ================ */ void rvVehicleDriver::Event_SetLeader( idEntity* newLeader ) { SetLeader( newLeader ); } /* ================ rvVehicleDriver::UpdateAutoCorrection ================ */ void rvVehicleDriver::UpdateAutoCorrection ( void ) { if ( IsDriving() ) { rvVehicle * vehicle = vehicleController.GetVehicle(); // Disregard your autocorrection state if we're slowing down to avoid collision with the leader. if ( vehicle->IsAutoCorrecting() ) { if ( avoidingLeader == true ) { vehicle->autoCorrectionBegin = 0; } } } } /* ================ rvVehicleDriver::Save ================ */ void rvVehicleDriver::Save ( idSaveGame *savefile ) const { savefile->Write ( &pathTargetInfo, sizeof ( pathTargetInfo ) ); savefile->Write ( &lastPathTargetInfo, sizeof ( lastPathTargetInfo ) ); savefile->WriteFloat ( currentThrottle ); faceTarget.Save ( savefile ); lookTarget.Save ( savefile ); leader.Save ( savefile ); savefile->WriteInt ( leaderFlags ); savefile->WriteFloat ( decelDistance ); savefile->WriteFloat ( minDistance ); savefile->WriteBool ( avoidingLeader ); savefile->WriteFloat ( fireEndTime ); func.Save ( savefile ); savefile->WriteBool ( isMoving ); savefile->WriteInt ( (int&)pathingMode ); pathingEntity.Save ( savefile ); savefile->WriteVec3 ( pathingOrigin ); savefile->WriteFloat( forwardThrustScale ); // cnicholson: Added unsaved var savefile->WriteFloat( rightThrustScale ); // cnicholson: Added unsaved var } /* ================ rvVehicleDriver::Restore ================ */ void rvVehicleDriver::Restore ( idRestoreGame *savefile ) { savefile->Read ( &pathTargetInfo, sizeof ( pathTargetInfo ) ); savefile->Read ( &lastPathTargetInfo, sizeof ( lastPathTargetInfo ) ); savefile->ReadFloat ( currentThrottle ); faceTarget.Restore ( savefile ); lookTarget.Restore ( savefile ); leader.Restore ( savefile ); savefile->ReadInt ( leaderFlags ); savefile->ReadFloat ( decelDistance ); savefile->ReadFloat ( minDistance ); savefile->ReadBool ( avoidingLeader ); savefile->ReadFloat ( fireEndTime ); func.Restore ( savefile ); savefile->ReadBool ( isMoving ); savefile->ReadInt ( (int&)pathingMode ); pathingEntity.Restore ( savefile ); savefile->ReadVec3 ( pathingOrigin ); savefile->ReadFloat( forwardThrustScale ); // cnicholson: Added unrestored var savefile->ReadFloat( rightThrustScale ); // cnicholson: Added unrestored var }