quake4-sdk/source/mpgame/vehicle/VehiclePosition.cpp

846 lines
21 KiB
C++

//----------------------------------------------------------------
// VehicleController.cpp
//
// Copyright 2002-2004 Raven Software
//----------------------------------------------------------------
#include "../../idlib/precompiled.h"
#pragma hdrstop
#include "../Game_local.h"
#include "Vehicle.h"
/*
================
rvVehiclePosition::rvVehiclePosition
================
*/
rvVehiclePosition::rvVehiclePosition ( void ) {
memset ( &mInputCmd, 0, sizeof(mInputCmd) );
mInputAngles.Zero ( );
memset ( &fl, 0, sizeof(fl) );
mCurrentWeapon = -1;
mSoundPart = -1;
mDriver = NULL;
mParent = NULL;
mEyeOrigin.Zero();
mEyeAxis.Identity();
mEyeOffset.Zero();
mEyeJoint = INVALID_JOINT;
mDriverOffset.Zero ( );
mDriverJoint = INVALID_JOINT;
}
/*
================
rvVehiclePosition::~rvVehiclePosition
================
*/
rvVehiclePosition::~rvVehiclePosition ( void ) {
mParts.DeleteContents ( true );
mWeapons.DeleteContents ( true );
}
void InitIntArray( const idVec3& vec, int array[] ) {
int ix;
for( ix = 0; ix < vec.GetDimension(); ++ix ) {
array[ix] = (int)vec[ix];
}
}
/*
================
rvVehiclePosition::Init
================
*/
void rvVehiclePosition::Init ( rvVehicle* parent, const idDict& args ) {
idVec3 garbage;
mParent = parent;
mEyeJoint = mParent->GetAnimator()->GetJointHandle ( args.GetString ( "eyeJoint" ) );
// abahr & twhitaker: removed this due to other changes.
//if( !mParent->GetAnimator()->GetJointTransform(mEyeJoint, gameLocal.GetTime(), garbage, mEyeJointTransform) ) {
mEyeJointTransform.Identity();
//}
//mEyeJointTransform.TransposeSelf();
mEyeOffset = args.GetVector ( "eyeOffset", "0 0 0" );
mDeltaEyeAxisScale = args.GetAngles( "deltaEyeAxisScale", "1 1 0" );
mDeltaEyeAxisScale.Clamp( ang_zero, idAngles(1.0f, 1.0f, 1.0f) );
InitIntArray( args.GetVector("eyeJointAxisMap", "0 1 2"), mEyeJointAxisMap );
InitIntArray( args.GetVector("eyeJointDirMap", "1 1 1"), mEyeJointDirMap );
mAxisOffset = args.GetAngles( "angles_offset" ).ToMat3();
mDriverJoint = mParent->GetAnimator()->GetJointHandle ( args.GetString ( "driverJoint" ) );
// abahr & twhitaker: removed this due to other changes.
//if( !mParent->GetAnimator()->GetJointTransform(mDriverJoint, gameLocal.GetTime(), garbage, mDriverJointTransform) ) {
mDriverJointTransform.Identity();
//}
//mDriverJointTransform.TransposeSelf( );
mDriverOffset = args.GetVector ( "driverOffset", "0 0 0" );
mDeltaDriverAxisScale = args.GetAngles( "deltaDriverAxisScale", "1 1 0" );
mDeltaDriverAxisScale.Clamp( ang_zero, idAngles(1.0f, 1.0f, 1.0f) );
InitIntArray( args.GetVector("driverJointAxisMap", "0 1 2"), mDriverJointAxisMap );
InitIntArray( args.GetVector("driverJointDirMap", "1 1 1"), mDriverJointDirMap );
mExitPosOffset = args.GetVector( "exitPosOffset" );
mExitAxisOffset = args.GetAngles( "exitAxisOffset" ).ToMat3();
mDriverAnim = args.GetString ( "driverAnim", "driver" );
fl.driverVisible = args.GetBool ( "driverVisible", "0" );
fl.engine = args.GetBool ( "engine", "0" );
fl.depthHack = args.GetBool ( "depthHack", "1" );
fl.bindDriver = args.GetBool ( "bindDriver", "1" );
args.GetString ( "internalSurface", "", mInternalSurface );
SetParts ( args );
mSoundMaxSpeed = args.GetFloat ( "maxsoundspeed", "0" );
// Looping sound when occupied?
if ( *args.GetString ( "snd_loop", "" ) ) {
mSoundPart = AddPart ( rvVehicleSound::GetClassType(), args );
static_cast<rvVehicleSound*>(mParts[mSoundPart])->SetAutoActivate ( false );
}
SelectWeapon ( 0 );
UpdateInternalView ( true );
}
/*
================
rvVehiclePosition::SetParts
================
*/
void rvVehiclePosition::SetParts ( const idDict& args ) {
const idKeyValue* kv;
// Spawn all parts
kv = args.MatchPrefix( "def_part", NULL );
while ( kv ) {
const idDict* dict;
idTypeInfo* typeInfo;
// Skip empty strings
if ( !kv->GetValue().Length() ) {
kv = args.MatchPrefix( "def_part", kv );
continue;
}
// Get the dictionary for the part
dict = gameLocal.FindEntityDefDict ( kv->GetValue() );
if ( !dict ) {
gameLocal.Error ( "Invalid vehicle part definition '%'", kv->GetValue().c_str() );
}
// Determine the part type
typeInfo = idClass::GetClass ( dict->GetString ( "spawnclass" ) );
if ( !typeInfo || !typeInfo->IsType ( rvVehiclePart::GetClassType() ) ) {
gameLocal.Error ( "Class '%s' is not a vehicle part", dict->GetString ( "spawnclass" ) );
}
// Add the new part
AddPart ( *typeInfo, *dict );
kv = args.MatchPrefix( "def_part", kv );
}
}
/*
================
rvVehiclePosition::AddPart
================
*/
int rvVehiclePosition::AddPart ( const idTypeInfo &classdef, const idDict& args ) {
rvVehiclePart* part;
int soundChannel;
// Get a sound channel
soundChannel = SND_CHANNEL_CUSTOM;
soundChannel += mParts.Num();
// Allocate the new part
part = static_cast<rvVehiclePart*>(classdef.CreateInstance ( ));
part->Init ( this, args, (s_channelType)soundChannel );
part->CallSpawn ( );
// Weapons go into their own list since only one can be active
// and any given point in time.
if ( part->IsType ( rvVehicleWeapon::GetClassType() ) ) {
return mWeapons.Append ( part );
}
return mParts.Append ( part );
}
/*
================
rvVehiclePosition::SetDriver
================
*/
bool rvVehiclePosition::SetDriver ( idActor* driver ) {
if ( mDriver == driver ) {
return false;
}
fl.inputValid = false;
if ( driver ) {
mDriver = driver;
// Keep the driver visible if the position is exposed
if ( fl.driverVisible ) {
mDriver->Show ( );
} else {
mDriver->Hide ( );
// Dont let the player take damage when inside the if not visible
mDriver->fl.takedamage = false;
}
if ( mSoundPart != -1 ) {
static_cast<rvVehicleSound*>(mParts[mSoundPart])->Play ( );
}
// Bind the driver to a joint or to the vehicles origin?
if( fl.bindDriver ) {
if ( INVALID_JOINT != mDriverJoint ) {
mDriver->GetPhysics()->SetAxis ( mParent->GetAxis( ) );// Not sure if this is needed
mDriver->BindToJoint ( mParent, mDriverJoint, true );
mDriver->GetPhysics()->SetOrigin ( vec3_origin );
} else {
mDriver->Bind ( mParent, true );
mDriver->GetPhysics()->SetOrigin( mDriverOffset );
}
} else {// If not bound put the vehicle first in the active list
mParent->activeNode.Remove();
mParent->activeNode.AddToFront( gameLocal.activeEntities );
}
// Play a certain animation on the driver
if ( mDriverAnim.Length() ) {
mDriver->GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, mDriver->GetAnimator()->GetAnim( mDriverAnim ), gameLocal.time, 0 );
}
} else {
if ( !fl.driverVisible ) {
mDriver->Show ( );
// Take damage again
mDriver->fl.takedamage = true;
}
// Driver is no longer bound to the vehicle
mDriver->Unbind();
mDriver = driver;
if ( mSoundPart != -1 ) {
static_cast<rvVehicleSound*>(mParts[mSoundPart])->Stop ( );
}
// Clear out the input commands so the guns dont keep firing and what not when
// the player gets out
memset ( &mInputCmd, 0, sizeof(mInputCmd) );
mInputAngles.Zero ( );
}
return true;
}
/*
================
rvVehiclePosition::GetAxis
================
*/
idMat3 rvVehiclePosition::GetAxis() const {
return mAxisOffset * GetParent()->GetPhysics()->GetAxis();
}
/*
================
rvVehiclePosition::GetOrigin
================
*/
idVec3 rvVehiclePosition::GetOrigin( const idVec3& offset ) const {
return GetParent()->GetPhysics()->GetOrigin() + (mDriverOffset + offset) * GetAxis();
}
/*
================
rvVehiclePosition::ActivateParts
================
*/
void rvVehiclePosition::ActivateParts ( bool active ) {
int i;
// Activate or deactive the parts based on whether there is a driver
for ( i = mParts.Num() - 1; i >= 0; i -- ) {
mParts[i]->Activate ( active );
}
for ( i = mWeapons.Num() - 1; i >= 0; i -- ) {
mWeapons[i]->Activate ( active );
}
}
/*
================
rvVehiclePosition::EjectDriver
================
*/
bool rvVehiclePosition::EjectDriver ( bool force ) {
if ( !IsOccupied ( ) ) {
return false;
}
// Physically eject the actor from the position
if ( !force && mParent->IsLocked ( ) ) {
mParent->IssueLockedWarning ( );
return false;
}
// Remove the driver and if successful disable all parts for
// this position
if ( SetDriver ( NULL ) ) {
ActivateParts ( false );
return true;
}
return false;
}
/*
================
rvVehiclePosition::SetInput
================
*/
void rvVehiclePosition::SetInput ( const usercmd_t& cmd, const idAngles& newAngles ) {
if ( gameDebug.IsHudActive ( DBGHUD_VEHICLE ) ) {
if ( mDriver == gameLocal.GetLocalPlayer ( ) ) {
gameDebug.SetFocusEntity ( mParent );
}
}
if ( fl.inputValid || !mDriver ) {
mOldInputCmd = mInputCmd;
mOldInputAngles = mInputAngles;
} else {
mOldInputCmd = cmd;
mInputCmd = cmd;
mInputAngles = newAngles;
// We have valid input now and there is a driver so activate all of the parts
if ( !fl.inputValid && mDriver ) {
ActivateParts ( true );
}
}
fl.inputValid = true;
mInputCmd = cmd;
mInputAngles = newAngles;
}
/*
================
rvVehiclePosition::GetInput
================
*/
void rvVehiclePosition::GetInput( usercmd_t& cmd, idAngles& newAngles ) const {
cmd = mInputCmd;
newAngles = mInputAngles;
}
/*
================
rvVehiclePosition::UpdateHUD
================
*/
void rvVehiclePosition::UpdateHUD ( idUserInterface* gui ) {
// HACK: twhitaker: this is ugly but since the GEV and the Walker now have a unified HUD, it is a necessary evil
int guiWeaponID;
if ( !stricmp( mParent->spawnArgs.GetString( "classname" ), "vehicle_walker" ) ) {
guiWeaponID = mCurrentWeapon + 2;
} else {
guiWeaponID = mCurrentWeapon;
}
gui->SetStateInt ( "vehicle_weapon", guiWeaponID );
// HACK: twhitaker end
gui->SetStateInt ( "vehicle_weaponcount", mWeapons.Num() );
if ( mCurrentWeapon >= 0 && mCurrentWeapon < mWeapons.Num() ) {
rvVehicleWeapon* weapon;
weapon = static_cast<rvVehicleWeapon*>(mWeapons[mCurrentWeapon]);
gui->SetStateFloat ( "vehicle_weaponcharge", weapon->GetCurrentCharge() );
gui->SetStateInt ( "vehicle_weaponammo", weapon->GetCurrentAmmo() );
}
// Calculate the rotation of the view in relation to the vehicle itself
gui->SetStateFloat ( "vehicle_rotate", -idMath::AngleDelta ( mEyeAxis.ToAngles()[YAW], mParent->GetAxis ( ).ToAngles()[YAW] ) );
if( GetParent() ) {
GetParent()->UpdateHUD( GetDriver(), gui );
}
}
/*
================
rvVehiclePosition::UpdateCursorGUI
================
*/
void rvVehiclePosition::UpdateCursorGUI ( idUserInterface* gui ) {
if ( mCurrentWeapon < 0 || mCurrentWeapon >= mWeapons.Num() ) {
return;
}
rvVehicleWeapon* weapon;
weapon = static_cast<rvVehicleWeapon*>(mWeapons[mCurrentWeapon]);
assert ( weapon );
weapon->UpdateCursorGUI ( gui );
}
/*
================
rvVehiclePosition::UpdateInternalView
================
*/
void rvVehiclePosition::UpdateInternalView ( bool force ) {
bool internal = false;
// If the local player is driving and not in third person then use internal
internal = ( mDriver == gameLocal.GetLocalPlayer() && !pm_thirdPerson.GetInteger() );
// force the update?
if ( !force && internal == fl.internalView ) {
return;
}
fl.internalView = internal;
if ( fl.depthHack ) {
mParent->GetRenderEntity()->weaponDepthHackInViewID = (IsOccupied() && fl.internalView) ? GetDriver()->entityNumber + 1 : 0;
}
// Show and hide the internal surface
if ( mInternalSurface.Length() ) {
mParent->ProcessEvent ( fl.internalView ? &EV_ShowSurface : &EV_HideSurface, mInternalSurface.c_str() );
}
}
/*
================
rvVehiclePosition::RunPrePhysics
================
*/
void rvVehiclePosition::RunPrePhysics ( void ) {
int i;
UpdateInternalView ( );
if ( mParent->IsStalled() ) {
if ( !fl.stalled ) {
// we just stalled
fl.stalled = true;
ActivateParts( false );
}
return;
} else if ( IsOccupied() && !mParent->IsStalled() && fl.stalled ) {
// we just restarted
fl.stalled = false;
ActivateParts( true );
}
// Give the parts a chance to set up for physics
for ( i = mParts.Num() - 1; i >= 0; i -- ) {
assert ( mParts[i] );
mParts[i]->RunPrePhysics ( );
}
if ( mCurrentWeapon >= 0 ) {
mWeapons[mCurrentWeapon]->RunPrePhysics ( );
}
// Run physics for each part
for ( i = mParts.Num() - 1; i >= 0; i -- ) {
assert ( mParts[i] );
mParts[i]->RunPhysics ( );
}
// Attenuate the attached sound if speed based
if ( mSoundPart != -1 && mSoundMaxSpeed > 0.0f ) {
float f;
float speed;
idVec3 vel;
// Only interested in forward or backward velocity
vel = mParent->GetPhysics()->GetLinearVelocity ( ) * mParent->GetPhysics()->GetAxis ( );
speed = idMath::ClampFloat ( 0.0f, mSoundMaxSpeed, vel.Normalize ( ) );
f = speed / mSoundMaxSpeed;
static_cast<rvVehicleSound*>(mParts[mSoundPart])->Attenuate ( f, f );
}
if ( mCurrentWeapon >= 0 ) {
mWeapons[mCurrentWeapon]->RunPhysics ( );
}
}
/*
================
rvVehiclePosition::RunPostPhysics
================
*/
void rvVehiclePosition::RunPostPhysics ( void ) {
int i;
for ( i = 0; i < mParts.Num(); i ++ ) {
assert ( mParts[i] );
mParts[i]->RunPostPhysics ( );
}
if ( mCurrentWeapon >= 0 ) {
mWeapons[mCurrentWeapon]->RunPostPhysics ( );
}
if ( !IsOccupied ( ) ) {
return;
}
if ( fl.inputValid ) {
if ( mInputCmd.upmove > 0 && !mParent->IsLocked() ) {
// inform the driver that its time to get out of the vehicle.
if( !mDriver->EventIsPosted(&AI_ExitVehicle) ) {
mDriver->PostEventMS( &AI_ExitVehicle, 250, false );// To remove jump when exiting
}
// If the position isnt occupied anymore then the vehicle exit was successful and there
// is nothing else to do
if ( !IsOccupied ( ) ) {
return;
}
} else if ( (mInputCmd.flags & UCF_IMPULSE_SEQUENCE) != (mOldInputFlags & UCF_IMPULSE_SEQUENCE) ) {
int i;
if ( mInputCmd.impulse >= IMPULSE_0 && mInputCmd.impulse <= IMPULSE_12 ) {
SelectWeapon( mInputCmd.impulse - IMPULSE_0 );
}
if ( mWeapons.Num () ) {
switch ( mInputCmd.impulse ) {
case IMPULSE_14:
SelectWeapon ( (mCurrentWeapon + mWeapons.Num() + 1) % mWeapons.Num() );
break;
case IMPULSE_15:
SelectWeapon ( (mCurrentWeapon + mWeapons.Num() - 1) % mWeapons.Num() );
break;
}
}
for( i = 0; i < mParts.Num(); i++ ) {
mParts[ i ]->Impulse ( mInputCmd.impulse );
}
mOldInputFlags = mInputCmd.flags;
}
}
// Update the eye origin and axis
GetEyePosition( mEyeOrigin, mEyeAxis );
if ( g_debugVehicle.GetInteger() == 2 ) {
gameRenderWorld->DebugArrow( colorMagenta, mEyeOrigin, mEyeOrigin + mEyeAxis[0] * 30.0f, 3 );
}
}
/*
================
rvVehiclePosition::GetEyePosition
================
*/
void rvVehiclePosition::GetEyePosition( idVec3& origin, idMat3& axis ) const {
GetPosition( mEyeJoint, mEyeOffset, mEyeJointTransform, mDeltaEyeAxisScale, mEyeJointAxisMap, mEyeJointDirMap, origin, axis );
axis *= GetParent()->GetAxis();
}
/*
================
rvVehiclePosition::GetDriverPosition
================
*/
void rvVehiclePosition::GetDriverPosition( idVec3& origin, idMat3& axis ) const {
if( fl.bindDriver ) {
GetPosition( mDriverJoint, mDriverOffset, mDriverJointTransform, mDeltaDriverAxisScale, mDriverJointAxisMap, mDriverJointDirMap, origin, axis );
} else {
origin = GetDriver()->GetPhysics()->GetOrigin();
axis = GetDriver()->viewAxis;
}
}
/*
================
rvVehiclePosition::GetPosition
================
*/
void rvVehiclePosition::GetPosition( const jointHandle_t jointHandle, const idVec3& offset, const idMat3& jointTransform, const idAngles& scale, const int axisMap[], const int dirMap[], idVec3& origin, idMat3& axis ) const {
if( GetParent()->GetAnimator()->GetJointTransform(jointHandle, gameLocal.GetTime(), origin, axis) ) {
axis *= jointTransform;
idAngles ang( axis.ToAngles().Remap(axisMap, dirMap).Scale(scale) );
axis = ang.Normalize360().ToMat3() * mAxisOffset;
origin = GetParent()->GetOrigin() + (origin + offset * axis) * GetParent()->GetAxis();
} else {
origin = GetOrigin( mEyeOffset );
axis = GetAxis();
}
}
/*
================
rvVehiclePosition::FireWeapon
================
*/
void rvVehiclePosition::FireWeapon( void ) {
if ( mCurrentWeapon >= 0 ) {
static_cast<rvVehicleWeapon*>( mWeapons[mCurrentWeapon] )->Fire();
}
}
/*
================
rvVehiclePosition::SelectWeapon
================
*/
void rvVehiclePosition::SelectWeapon ( int weapon ) {
if ( weapon < 0 || weapon >= mWeapons.Num ( ) ) {
return;
}
// mekberg: clear effect
if ( mCurrentWeapon != -1 ) {
static_cast<rvVehicleWeapon*> ( mWeapons[ mCurrentWeapon ] )->StopTargetEffect( );
}
mCurrentWeapon = weapon;
}
/*
================
rvVehiclePosition::WriteToSnapshot
================
*/
void rvVehiclePosition::WriteToSnapshot ( idBitMsgDelta &msg ) const {
msg.WriteBits ( mCurrentWeapon, 3 );
}
/*
================
rvVehiclePosition::ReadFromSnapshot
================
*/
void rvVehiclePosition::ReadFromSnapshot ( const idBitMsgDelta &msg ) {
mCurrentWeapon = msg.ReadBits ( 3 );
}
/*
================
rvVehiclePosition::Save
================
*/
void rvVehiclePosition::Save ( idSaveGame* savefile ) const {
int i;
savefile->WriteUsercmd( mInputCmd );
savefile->WriteAngles ( mInputAngles );
savefile->WriteUsercmd ( mOldInputCmd );
savefile->WriteAngles ( mOldInputAngles );
savefile->WriteInt ( mOldInputFlags );
savefile->WriteInt ( mCurrentWeapon );
mDriver.Save ( savefile );
mParent.Save ( savefile );
savefile->WriteInt ( mParts.Num ( ) );
for ( i = 0; i < mParts.Num(); i ++ ) {
savefile->WriteString ( mParts[i]->GetClassname() );
savefile->WriteStaticObject ( *mParts[i] );
}
savefile->WriteInt ( mWeapons.Num ( ) );
for ( i = 0; i < mWeapons.Num(); i ++ ) {
savefile->WriteString ( mWeapons[i]->GetClassname() );
savefile->WriteStaticObject ( *mWeapons[i] );
}
savefile->WriteVec3 ( mEyeOrigin );
savefile->WriteMat3 ( mEyeAxis );
savefile->WriteJoint ( mEyeJoint );
savefile->WriteVec3 ( mEyeOffset );
savefile->WriteMat3( mEyeJointTransform );
savefile->WriteAngles( mDeltaEyeAxisScale );
savefile->Write ( mEyeJointAxisMap, sizeof mEyeJointAxisMap );
savefile->Write ( mEyeJointDirMap, sizeof mEyeJointDirMap );
savefile->WriteMat3 ( mAxisOffset );
savefile->WriteJoint ( mDriverJoint );
savefile->WriteVec3 ( mDriverOffset );
savefile->WriteMat3 ( mDriverJointTransform );
savefile->WriteAngles( mDeltaDriverAxisScale );
savefile->Write ( mDriverJointAxisMap, sizeof mDriverJointAxisMap );
savefile->Write ( mDriverJointDirMap, sizeof mDriverJointDirMap );
savefile->WriteVec3 ( mExitPosOffset );
savefile->WriteMat3 ( mExitAxisOffset );
savefile->WriteString ( mDriverAnim );
savefile->WriteString ( mInternalSurface );
savefile->Write ( &fl, sizeof ( fl ) );
savefile->WriteInt ( mSoundPart );
savefile->WriteFloat ( mSoundMaxSpeed );
}
/*
================
rvVehiclePosition::Restore
================
*/
void rvVehiclePosition::Restore ( idRestoreGame* savefile ) {
int i;
int num;
savefile->ReadUsercmd( mInputCmd );
savefile->ReadAngles ( mInputAngles );
savefile->ReadUsercmd ( mOldInputCmd );
savefile->ReadAngles ( mOldInputAngles );
savefile->ReadInt ( mOldInputFlags );
savefile->ReadInt ( mCurrentWeapon );
mDriver.Restore ( savefile );
mParent.Restore ( savefile );
savefile->ReadInt ( num );
mParts.Clear ( );
mParts.SetNum ( num );
for ( i = 0; i < num; i ++ ) {
idStr spawnclass;
rvVehiclePart* part;
idTypeInfo* typeInfo;
savefile->ReadString ( spawnclass );
// Determine the part type
typeInfo = idClass::GetClass ( spawnclass );
if ( !typeInfo || !typeInfo->IsType ( rvVehiclePart::GetClassType() ) )
{
gameLocal.Error ( "Class '%s' is not a vehicle part", spawnclass.c_str() );
}
part = static_cast<rvVehiclePart*>(typeInfo->CreateInstance ( ));
savefile->ReadStaticObject ( *part );
mParts[i] = part;
}
savefile->ReadInt ( num );
mWeapons.Clear ( );
mWeapons.SetNum ( num );
for ( i = 0; i < num; i ++ ) {
idStr spawnclass;
rvVehiclePart* part;
idTypeInfo* typeInfo;
savefile->ReadString ( spawnclass );
// Determine the part type
typeInfo = idClass::GetClass ( spawnclass );
if ( !typeInfo || !typeInfo->IsType ( rvVehiclePart::GetClassType() ) )
{
gameLocal.Error ( "Class '%s' is not a vehicle part", spawnclass.c_str() );
}
part = static_cast<rvVehiclePart*>(typeInfo->CreateInstance ( ));
savefile->ReadStaticObject ( *part );
mWeapons[i] = part;
}
savefile->ReadVec3 ( mEyeOrigin );
savefile->ReadMat3 ( mEyeAxis );
savefile->ReadJoint ( mEyeJoint );
savefile->ReadVec3 ( mEyeOffset );
savefile->ReadMat3 ( mEyeJointTransform );
savefile->ReadAngles( mDeltaEyeAxisScale );
savefile->Read( mEyeJointAxisMap, sizeof mEyeJointAxisMap );
savefile->Read( mEyeJointDirMap, sizeof mEyeJointDirMap );
savefile->ReadMat3 ( mAxisOffset );
savefile->ReadJoint ( mDriverJoint );
savefile->ReadVec3 ( mDriverOffset );
savefile->ReadMat3 ( mDriverJointTransform );
savefile->ReadAngles( mDeltaDriverAxisScale );
savefile->Read( mDriverJointAxisMap, sizeof mDriverJointAxisMap );
savefile->Read( mDriverJointDirMap, sizeof mDriverJointDirMap );
savefile->ReadVec3 ( mExitPosOffset );
savefile->ReadMat3 ( mExitAxisOffset );
savefile->ReadString ( mDriverAnim );
savefile->ReadString ( mInternalSurface );
savefile->Read ( &fl, sizeof(fl) );
savefile->ReadInt ( mSoundPart );
savefile->ReadFloat ( mSoundMaxSpeed );
}