//---------------------------------------------------------------- // Vehicle.cpp // // Copyright 2002-2004 Raven Software //---------------------------------------------------------------- #include "../../idlib/precompiled.h" #pragma hdrstop #include "../Game_local.h" #include "../Effect.h" #include "../ai/AI_Manager.h" #include "../Projectile.h" #define VEHICLE_CRASH_DELAY 500 #define VEHICLE_HAZARD_TIMEOUT 5000 #define VEHICLE_LOCK_TIMEOUT 5000 const idEventDef EV_LaunchProjectiles( "launchProjectiles", "d" ); const idEventDef EV_HUDShockWarningOff( ""); const idEventDef EV_StalledRestart( "", "dd" ); const idEventDef EV_GetViewAngles( "getViewAngles", NULL, 'v' ); CLASS_DECLARATION( idActor, rvVehicle ) EVENT( EV_Door_Lock, rvVehicle::Event_Lock ) EVENT( EV_Door_IsLocked, rvVehicle::Event_IsLocked ) EVENT( AI_EnableMovement, rvVehicle::Event_EnableMovement ) EVENT( AI_DisableMovement, rvVehicle::Event_DisableMovement ) EVENT( EV_Player_EnableWeapon, rvVehicle::Event_EnableWeapon ) EVENT( EV_Player_DisableWeapon, rvVehicle::Event_DisableWeapon ) EVENT( AI_EnableClip, rvVehicle::Event_EnableClip ) EVENT( AI_DisableClip, rvVehicle::Event_DisableClip ) EVENT( EV_Activate, rvVehicle::Event_Activate ) EVENT( EV_LaunchProjectiles, rvVehicle::Event_LaunchProjectiles ) EVENT( AI_SetScript, rvVehicle::Event_SetScript ) EVENT( AI_SetHealth, rvVehicle::Event_SetHealth ) EVENT( EV_HUDShockWarningOff, rvVehicle::Event_HUDShockWarningOff ) EVENT( EV_StalledRestart, rvVehicle::Event_StalledRestart ) EVENT( EV_GetViewAngles, rvVehicle::Event_GetViewAngles ) END_CLASS /* ===================== rvVehicle::rvVehicle ===================== */ rvVehicle::rvVehicle ( void ) { autoRight = false; hud = NULL; shieldModel = NULL; shieldMaxHealth = 0; hazardWarningTime = 0; lockWarningTime = 0; godModeDamage = 0; drivers = 0; autoCorrectionBegin = 0; crashEffect = 0; crashTime = 0; crashNextSound = 0; fl.networkSync = true; } rvVehicle::~rvVehicle ( void ) { int i; // Force all the drivers out for ( i = 0; i < positions.Num(); i ++ ) { idActor* driver = positions[i].GetDriver ( ); if ( !driver ) { continue; } driver->ProcessEvent ( &AI_ExitVehicle, true ); } if ( shieldModel ) { shieldModel->Unlink(); delete shieldModel; } positions.Clear ( ); } /* ================ rvVehicle::Spawn ================ */ void rvVehicle::Spawn( void ) { const char* temp; memset ( &vfl, 0, sizeof(vfl) ); SetPositions ( ); healthMax = health; healthLow = spawnArgs.GetInt ( "lowhealth", va("%d", health / 10 ) ); damageStaticChance = spawnArgs.GetFloat ( "damageStaticChance", "0" ); crashSpeedSmall = spawnArgs.GetFloat ( "crashSpeedSmall", "50" ); crashSpeedMedium = spawnArgs.GetFloat ( "crashSpeedMedium", "125" ); crashSpeedLarge = spawnArgs.GetFloat ( "crashSpeedLarge", "200" ); crashDamage = spawnArgs.GetString ( "def_crashDamage" ); healthRegenDelay = SEC2MS(spawnArgs.GetFloat( "healthRegenDelay", "0" )); healthRegenRate = spawnArgs.GetInt( "healthRegenRate", "0" ); healthRegenAmount.Init( gameLocal.time, 0, healthMax, healthMax ); vfl.disableMovement = spawnArgs.GetBool ( "disableMovement", "0" ); vfl.disableWeapons = spawnArgs.GetBool ( "disableWeapon", "0" ); vfl.scripted = 0; vfl.flipEject = spawnArgs.GetBool( "allowFlipEject", "1" ); health = spawnArgs.GetInt ( "health", "100" ); fl.takedamage = ( health > 0 ); // Load the HUD if ( NULL != ( temp = spawnArgs.GetString( "gui_hud", "" ) ) ) { hud = uiManager->FindGui( temp, true, false, false ); if ( hud ) { hud->SetStateInt ( "vehicle_id", spawnArgs.GetInt ( "hudid" ) ); hud->Activate( true, gameLocal.time ); } } // Get shield parameters shieldMaxHealth = spawnArgs.GetInt ( "shieldHealth", "0" ); shieldRegenTime = SEC2MS ( spawnArgs.GetFloat ( "shieldRegenTime", "0" ) ); shieldRegenDelay = SEC2MS ( spawnArgs.GetFloat ( "shieldRegenDelay", "0" ) ); shieldHitTime = 0; shieldHealth.Init ( gameLocal.time, 0, shieldMaxHealth, shieldMaxHealth ); SetCombatModel(); cachedContents = GetPhysics()->GetContents(); funcs.enter.Init( spawnArgs.GetString( "enter_script" ) ); funcs.exit.Init( spawnArgs.GetString( "exit_script" ) ); crashVelocitySmall = spawnArgs.GetFloat( "crashVelocitySmall", "0" ); crashVelocityMedium = spawnArgs.GetFloat( "crashVelocityMedium", "0" ); crashVelocityLarge = spawnArgs.GetFloat( "crashVelocityLarge", "0" ); alwaysImpactDamage = spawnArgs.GetBool( "alwaysImpactDamage", "0" ); // precache hard-coded entitydefs declManager->FindType( DECL_ENTITYDEF, "damage_gev_collision_self", false ); declManager->FindType( DECL_ENTITYDEF, "damage_gev_collision", false ); } /* ================ rvVehicle::Save ================ */ void rvVehicle::Save ( idSaveGame *savefile ) const { int i; savefile->WriteInt ( positions.Num ( ) ); for ( i = 0; i < positions.Num(); i ++ ) { positions[i].Save ( savefile ); } savefile->WriteInt ( drivers ); savefile->WriteUserInterface( hud, false ); savefile->WriteFloat ( crashSpeedSmall ); savefile->WriteFloat ( crashSpeedMedium ); savefile->WriteFloat ( crashSpeedLarge ); savefile->WriteString ( crashDamage ); crashEffect.Save ( savefile ); savefile->WriteInt ( crashNextSound ); savefile->WriteInt ( crashTime ); savefile->WriteFloat ( autoRightDir ); savefile->WriteBool ( autoRight ); savefile->WriteInt( autoCorrectionBegin ); savefile->Write( &vfl, sizeof( vfl ) ); savefile->WriteFloat ( damageStaticChance ); savefile->WriteFloat ( shieldMaxHealth ); savefile->WriteInterpolate( shieldHealth ); savefile->WriteInt ( shieldHitTime ); savefile->WriteFloat ( shieldRegenTime ); savefile->WriteInt ( shieldRegenDelay ); // TOSAVE: idClipModel* shieldModel; savefile->WriteInt( healthRegenDelay ); savefile->WriteInt( healthRegenRate ); savefile->WriteInterpolate( healthRegenAmount ); savefile->WriteInt ( hazardWarningTime ); savefile->WriteInt ( lockWarningTime ); savefile->WriteInt ( healthMax ); savefile->WriteInt ( healthLow ); savefile->WriteInt ( godModeDamage ); savefile->WriteInt ( cachedContents ); funcs.enter.Save( savefile ); funcs.exit.Save ( savefile ); // cnicholson: Don't save crash Velocities, they are assigned in Restore // savefile->WriteFloat( crashVelocitySmall ); // cnicholson: Added unsaved var // savefile->WriteFloat( crashVelocityMedium );// cnicholson: Added unsaved var // savefile->WriteFloat( crashVelocityLarge ); // cnicholson: Added unsaved var // TOSAVE: idList< idEntityPtr< idGuidedProjectile > > incomingProjectiles; } /* ================ rvVehicle::Restore ================ */ void rvVehicle::Restore ( idRestoreGame *savefile ) { int i; int num; savefile->ReadInt ( num ); positions.Clear(); positions.SetNum ( num ); for ( i = 0; i < num; i ++ ) { positions[i].Restore ( savefile ); } savefile->ReadInt ( drivers ); savefile->ReadUserInterface( hud, &spawnArgs ); savefile->ReadFloat ( crashSpeedSmall ); savefile->ReadFloat ( crashSpeedMedium ); savefile->ReadFloat ( crashSpeedLarge ); savefile->ReadString ( crashDamage ); crashEffect.Restore ( savefile ); savefile->ReadInt ( crashNextSound ); savefile->ReadInt ( crashTime ); savefile->ReadFloat ( autoRightDir ); savefile->ReadBool ( autoRight ); savefile->ReadInt( (int&)autoCorrectionBegin ); savefile->Read( &vfl, sizeof( vfl ) ); savefile->ReadFloat ( damageStaticChance ); savefile->ReadFloat ( shieldMaxHealth ); savefile->ReadInterpolate( shieldHealth ); savefile->ReadInt ( shieldHitTime ); savefile->ReadFloat ( shieldRegenTime ); savefile->ReadInt ( shieldRegenDelay ); // TORESTORE: idClipModel* shieldModel; savefile->ReadInt( healthRegenDelay ); savefile->ReadInt( healthRegenRate ); savefile->ReadInterpolate( healthRegenAmount ); savefile->ReadInt ( hazardWarningTime ); savefile->ReadInt ( lockWarningTime ); savefile->ReadInt ( healthMax ); savefile->ReadInt ( healthLow ); savefile->ReadInt ( godModeDamage ); savefile->ReadInt ( cachedContents ); funcs.enter.Restore ( savefile ); funcs.exit.Restore ( savefile ); SetCombatModel ( ); crashVelocitySmall = spawnArgs.GetFloat( "crashVelocitySmall", "0" ); crashVelocityMedium = spawnArgs.GetFloat( "crashVelocityMedium", "0" ); crashVelocityLarge = spawnArgs.GetFloat( "crashVelocityLarge", "0" ); alwaysImpactDamage = spawnArgs.GetBool( "alwaysImpactDamage", "0" ); // precache hard-coded entitydefs declManager->FindType( DECL_ENTITYDEF, "damage_gev_collision_self", false ); declManager->FindType( DECL_ENTITYDEF, "damage_gev_collision", false ); } /* ================ rvVehicle::SetPositions ================ */ void rvVehicle::SetPositions ( void ) { int positionCount; int index; const idKeyValue* kv; // Count the positions first so we can allocate them all at once positionCount = 0; kv = spawnArgs.MatchPrefix( "def_position", NULL ); while ( kv ) { positionCount++; kv = spawnArgs.MatchPrefix( "def_position", kv ); } // Every vehicle needs a def_position in it's def file if ( positionCount == 0) { //gameLocal.Error ( "Vehicle '%s' has no def_position entries.", name.c_str() ); gameLocal.Warning ( "Vehicle '%s' has no def_position entries.", name.c_str() ); positionCount = 1; spawnArgs.Set( "def_position", "vehicle_ai_null_position" ); } // Initialize the positions positions.SetNum ( positionCount ); // Initialize all of the positions index = 0; kv = spawnArgs.MatchPrefix( "def_position", NULL ); while ( kv ) { const idDict* dict; // Get the position dictionary dict = gameLocal.FindEntityDefDict ( kv->GetValue(), false ); if ( !dict ) { gameLocal.Error ( "Invalid vehicle part definition '%'", kv->GetValue().c_str() ); } // Initialize the position positions[index++].Init ( this, *dict ); kv = spawnArgs.MatchPrefix( "def_position", kv ); } } /* ================ rvVehicle::SetCombatModel ================ */ void rvVehicle::SetCombatModel ( void ) { idActor::SetCombatModel ( ); if ( shieldMaxHealth ) { idBounds bounds; bounds.Clear ( ); bounds.AddPoint ( spawnArgs.GetVector ( "shieldMins", "0 0 0" ) ); bounds.AddPoint ( spawnArgs.GetVector ( "shieldMaxs", "0 0 0" ) ); if ( shieldModel ) { shieldModel->Unlink(); delete shieldModel; shieldModel = NULL; } //twhitaker: dodecahedron support idStr shieldModelName; if ( spawnArgs.GetString( "shieldModel", "", shieldModelName ) ) { if ( shieldModelName.Length() && !shieldModelName.Icmp( "dodecahedron" ) ) { idTraceModel trm; trm.SetupDodecahedron ( GetPhysics()->GetBounds() ); shieldModel = new idClipModel ( trm ); } } //twhitaker: end if ( !shieldModel ) { shieldModel = new idClipModel ( idTraceModel ( bounds, spawnArgs.GetInt ( "shieldSides", "6" ) ) ); } shieldModel->SetOwner ( this ); shieldModel->SetContents ( CONTENTS_SOLID ); } else { shieldModel = NULL; } } /* ================ rvVehicle::LinkCombat ================ */ void rvVehicle::LinkCombat ( void ) { if ( fl.hidden ) { return; } if ( g_debugVehicle.GetInteger() == 1 && shieldModel ) { collisionModelManager->DrawModel( shieldModel->GetCollisionModel(), renderEntity.origin, renderEntity.axis, vec3_origin, mat3_identity, 0.0f ); } if ( shieldHealth.GetCurrentValue ( gameLocal.time ) > 0 && HasDrivers ( ) ) { // RAVEN BEGIN // ddynerman: multiple clip worlds shieldModel->Link( this, 0, renderEntity.origin, renderEntity.axis ); // RAVEN END if ( combatModel ) { combatModel->Unlink ( ); } } else { // RAVEN BEGIN // ddynerman: multiple clip worlds combatModel->Link( this, 0, renderEntity.origin, renderEntity.axis, modelDefHandle ); // RAVEN END if ( shieldModel ) { shieldModel->Unlink ( ); } } } /* ================ rvVehicle::ClientPredictionThink ================ */ void rvVehicle::ClientPredictionThink ( void ) { Think ( ); } /* ================ rvVehicle::WriteToSnapshot ================ */ void rvVehicle::WriteToSnapshot ( idBitMsgDelta &msg ) const { int i; // TODO: Check that this conditional write to delta message is OK for ( i = 0; i < positions.Num(); i ++ ) { positions[i].WriteToSnapshot ( msg ); } } /* ================ rvVehicle::ReadFromSnapshot ================ */ void rvVehicle::ReadFromSnapshot ( const idBitMsgDelta &msg ) { int i; for ( i = 0; i < positions.Num(); i ++ ) { positions[i].ReadFromSnapshot ( msg ); } } /* ================ rvVehicle::UpdateState ================ */ void rvVehicle::UpdateState ( void ) { rvVehiclePosition* pos; pos = &positions[0]; vfl.forward = (pos->IsOccupied() && pos->mInputCmd.forwardmove > 0); vfl.backward = (pos->IsOccupied() && pos->mInputCmd.forwardmove < 0); vfl.right = (pos->IsOccupied() && pos->mInputCmd.rightmove > 0); vfl.left = (pos->IsOccupied() && pos->mInputCmd.rightmove < 0); vfl.driver = pos->IsOccupied(); vfl.strafe = (pos->IsOccupied() && pos->mInputCmd.buttons & BUTTON_STRAFE ); } /* ================ rvVehicle::Think ================ */ void rvVehicle::Think ( void ) { UpdateState(); UpdateAnimState ( ); UpdateIncomingProjectiles(); // If we are the current debug entity then output some info to the hud if ( gameDebug.IsHudActive ( DBGHUD_VEHICLE, this ) ) { gameDebug.SetInt ( "vehicle", 1 ); gameDebug.SetInt ( "shields", shieldHealth.GetCurrentValue(gameLocal.time) ); gameDebug.SetInt ( "positions", positions.Num() ); gameDebug.SetInt ( "drivers", drivers ); } if ( g_debugVehicle.GetInteger() == 1 ) { idMat3 flatAxis = idAngles(0,GetPhysics()->GetAxis().ToAngles().yaw,0).ToMat3(); gameRenderWorld->DebugLine ( colorOrange, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() + 50.0f * flatAxis[0] ); gameRenderWorld->DebugLine ( colorYellow, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() + 50.0f * flatAxis[1] ); gameRenderWorld->DebugLine ( colorCyan, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() - 50.0f * flatAxis[2] ); gameRenderWorld->DebugLine ( colorRed, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() + 50.0f * GetPhysics()->GetAxis()[0] ); gameRenderWorld->DebugLine ( colorGreen, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() + 50.0f * GetPhysics()->GetAxis()[1] ); gameRenderWorld->DebugLine ( colorBlue, GetPhysics()->GetCenterMass(), GetPhysics()->GetCenterMass() + 50.0f * GetPhysics()->GetAxis()[2] ); } /* VEH_FIXME: where to put this? // For engine glow renderEntity.shaderParms[7] = mEngineStatus.GetCurrentValue ( gameLocal.time ); */ if( thinkFlags & TH_THINK ) { int i; for( i = 0; i < positions.Num(); i++ ) { positions[i].RunPrePhysics( ); } if( autoRight ) { if( idMath::Fabs( idMath::AngleNormalize180( renderEntity.axis.ToAngles().roll ) ) < 30.0f ) { GetPhysics()->SetLinearVelocity( vec3_origin ); autoRight = false; } else { idVec3 upSpeed = -GetPhysics()->GetGravityNormal() * 72.0f; idMat3 axis = GetPhysics()->GetAxis(); // Rotate around the forward axis axis.RotateRelative ( 0, (renderEntity.axis.ToAngles().roll<0?180:-180) * MS2SEC(gameLocal.GetMSec()) * 1.5f ); GetPhysics()->SetAxis ( axis ); // Move up to make room for the rotation GetPhysics()->SetLinearVelocity( upSpeed ); } } // twhitaker: nothing special here RunPrePhysics(); // Run the physics RunPhysics(); // twhitaker: nothing special here RunPostPhysics(); // Give each position a chance to think after physics has been run vfl.godmode = false; fl.notarget = false; for( i = 0; i < positions.Num(); i++ ) { positions[i].RunPostPhysics( ); // Include the eye origin into the bounds to ensure the driver // is inside the render bounds when the vehicle is rendered. if ( positions[i].IsOccupied ( ) ) { AddToBounds ( positions[i].GetEyeOrigin ( ) ); // Transfer godmode from driving players if ( positions[i].mDriver && positions[i].mDriver->IsType ( idPlayer::GetClassType() ) ) { if ( static_cast(positions[i].mDriver.GetEntity())->godmode ) { vfl.godmode = true; } } // Inherit notarget if our driver has it on if ( positions[i].mDriver && positions[i].mDriver->fl.notarget ) { fl.notarget = true; } } } // If the vehicle is flipped then kick out all drivers if ( HasDrivers() && IsFlipped ( ) && GetPhysics()->GetLinearVelocity ( ).LengthSqr() < (100.0f * 100.0f)) { if ( spawnArgs.GetBool( "locked_flip_death", false ) ) { Killed( this, this, 999999999, GetPhysics()->GetLinearVelocity(), 0 ); } else if ( vfl.flipEject ) { for ( i = 0; i < positions.Num(); i ++ ) { idActor* driver = positions[i].GetDriver (); if ( driver ) { driver->ProcessEvent ( &AI_ExitVehicle, true ); } } } } } // Regenerate the shield if ( shieldMaxHealth && shieldHealth.GetCurrentValue(gameLocal.time) < shieldMaxHealth ) { if ( gameLocal.time > shieldHitTime + shieldRegenDelay && shieldHealth.IsDone ( gameLocal.time ) ) { StopSound ( SND_CHANNEL_BODY2, false ); StartSound ( "snd_shieldRecharge", SND_CHANNEL_BODY2, 0, false, NULL ); float regenTime = shieldHealth.GetCurrentValue(gameLocal.time); regenTime /= (float)shieldMaxHealth; regenTime = 1.0f - regenTime; regenTime *= (float)shieldRegenTime; shieldHealth.Init ( gameLocal.time, regenTime, shieldHealth.GetCurrentValue(gameLocal.time), shieldMaxHealth ); } } // Regenerate health, if needed // do nothing if the vehicle is at full hit points if (health < healthMax) { // also do nothing if we've been damaged less than healthRegenDelay seconds ago if ( healthRegenRate && shieldHitTime + healthRegenDelay <= gameLocal.time ) { // do we need to start the interpolation? if ( healthRegenAmount.IsDone( gameLocal.time )) { healthRegenAmount.Init( gameLocal.time, SEC2MS((float)(healthMax - health)/healthRegenRate), health, healthMax ); } else // get our interpolated health value for the current time. { health = healthRegenAmount.GetCurrentValue( gameLocal.time ); } } } // Check hazards if ( hazardWarningTime && gameLocal.time > hazardWarningTime + VEHICLE_HAZARD_TIMEOUT ) { hazardWarningTime = 0; StartSound ( "snd_voiceSafe", SND_CHANNEL_VOICE, 0, false, NULL ); if ( renderEntity.gui[0] ) { renderEntity.gui[0]->HandleNamedEvent ( "info_safe" ); } } // Stop the crash effect if no collide happened this frame if ( crashEffect && crashTime != gameLocal.time ) { crashEffect->Stop ( ); crashEffect = NULL; } UpdateAnimation(); if ( thinkFlags & TH_UPDATEVISUALS ) { Present(); LinkCombat(); } if (spawnArgs.GetBool("touchtriggers")) { TouchTriggers(); } } /* ================ rvVehicle::UpdateDrivers ================ */ void rvVehicle::UpdateDrivers ( int delta ) { int oldDrivers = drivers; drivers = idMath::ClampInt ( 0, positions.Num(), drivers + delta ); if ( drivers && !oldDrivers ) { if ( fl.takedamage ) { aiManager.AddTeammate ( this ); } // script function for spawning guys const char* temp; if( spawnArgs.GetString( "call_enter", "", &temp ) && *temp ) { gameLocal.CallFrameCommand ( this, temp ); } } else if ( !drivers ) { aiManager.RemoveTeammate ( this ); refSound.listenerId = entityNumber + 1; // script function for spawning guys const char* temp; if( spawnArgs.GetString( "call_exit", "", &temp ) && *temp ) { gameLocal.CallFrameCommand ( this, temp ); } } } /* ================ rvVehicle::GetAxis ================ */ const idMat3& rvVehicle::GetAxis( int id ) const { return GetPhysics()->GetAxis( id ); } /* ================ rvVehicle::GetOrigin ================ */ const idVec3& rvVehicle::GetOrigin( int id ) const { return GetPhysics()->GetOrigin(); } /* ================ rvVehicle::GetEyePosition ================ */ void rvVehicle::GetEyePosition ( int pos, idVec3& origin, idMat3& axis ) { axis = positions[pos].GetEyeAxis ( ); origin = positions[pos].GetEyeOrigin ( ); } /* ================ rvVehicle::GetDriverPosition ================ */ void rvVehicle::GetDriverPosition ( int pos, idVec3& origin, idMat3& axis ) { const rvVehiclePosition *position = GetPosition( pos ); position->GetDriverPosition( origin, axis ); } /* ===================== rvVehicle::FindClearExitPoint ===================== */ // FIXME: this whole function could be cleaned up bool rvVehicle::FindClearExitPoint( int pos, idVec3& origin, idMat3& axis ) const { trace_t trace; const rvVehiclePosition* position = GetPosition( pos ); idActor* driver = position->GetDriver(); idVec3 end; idVec3 traceOffsetPoints[4]; const float error = 1.1f; origin.Zero(); axis.Identity(); idMat3 driverAxis = driver->viewAxis; idVec3 driverOrigin = driver->GetPhysics()->GetOrigin(); idMat3 vehicleAxis = position->GetEyeAxis(); idVec3 vehicleOrigin = GetPhysics()->GetOrigin(); idBounds driverBounds( driver->GetPhysics()->GetBounds() ); idBounds vehicleBounds( GetPhysics()->GetBounds() ); idBounds driverAbsBounds; idBounds vehicleAbsBounds; idMat3 identity; identity.Identity( ); if( position->fl.driverVisible ) { // May want to do this even if the driver isn't visible if( position->mExitPosOffset.LengthSqr() > VECTOR_EPSILON ) { axis = GetPhysics()->GetAxis() * position->mExitAxisOffset; origin = vehicleOrigin + position->mExitPosOffset * axis; } else { origin = driverOrigin; axis = (driver->IsBoundTo(this)) ? vehicleAxis : driverAxis; } return true; } // Build list // FIXME: try and find a cleaner way to do this traceOffsetPoints[ 0 ] = vehicleBounds.FindVectorToEdge( vehicleAxis[ 1 ] ) - driverBounds.FindVectorToEdge( -vehicleAxis[ 1 ] ); traceOffsetPoints[ 1 ] = vehicleBounds.FindVectorToEdge( -vehicleAxis[ 1 ] ) - driverBounds.FindVectorToEdge( vehicleAxis[ 1 ] ); traceOffsetPoints[ 2 ] = vehicleBounds.FindVectorToEdge( vehicleAxis[ 0 ] ) - driverBounds.FindVectorToEdge( -vehicleAxis[ 0 ] ); traceOffsetPoints[ 3 ] = vehicleBounds.FindVectorToEdge( -vehicleAxis[ 0 ] ) - driverBounds.FindVectorToEdge( vehicleAxis[ 0 ] ); for( int ix = 0; ix < 4; ++ix ) { //Try all four sides and on top if need be end = vehicleOrigin + traceOffsetPoints[ ix ] * error; // RAVEN BEGIN // ddynerman: multiple clip worlds gameLocal.Translation( this, trace, vehicleOrigin, end, driver->GetPhysics()->GetClipModel(), driverAxis, driver->GetPhysics()->GetClipMask(), this, driver ); // RAVEN END driverAbsBounds.FromTransformedBounds( driverBounds, trace.endpos, driverAxis ); // mekberg: vehicle bounds are resized based on rotation but not rotated with the axis. Get transformed bounds. vehicleAbsBounds.FromTransformedBounds( vehicleBounds, vehicleOrigin, identity ); if( trace.fraction > 0.0f && !driverAbsBounds.IntersectsBounds(vehicleAbsBounds) ) { origin = trace.endpos; axis = vehicleAxis; return true; } } return false; } /* ================ rvVehicle::AddDriver Add a driver to the vehicle at the given position. Its possible that the given position is occupied, in that case the driver will be assigned the closest valid position. If no position can be found then (-1) will be returned, otherwise the position the driver is now driving will be returned. ================ */ int rvVehicle::AddDriver ( int position, idActor* driver ) { int wraparound; // Ensure the given position is valid if ( position < 0 || position >= positions.Num() ) { gameLocal.Warning ( "position %d is invalid for vehicle '%s'", position, name.c_str() ); return -1; } wraparound = position; do { // If the position isnt occupied then set the driver if ( !positions[position].IsOccupied ( ) ) { positions[position].SetDriver ( driver ); // Vehicle is on same team as driver team = driver->team; UpdateDrivers ( 1 ); // The local player will hear all private sounds of the vehicle if ( driver == gameLocal.GetLocalPlayer ( ) ) { refSound.listenerId = driver->GetListenerId ( ); } return position; } // Check the next position, wrap around if necessary position = (position + 1) % positions.Num(); } while ( wraparound != position ); return -1; } /* ================ rvVehicle::RemoveDriver Removes the given driver from the vehicle. This includes physically taking the driver out of the vehicle and placing them back into the world and will follow all constraints that limit the driver from exiting the vehicle. ================ */ bool rvVehicle::RemoveDriver ( int position, bool force ) { if ( !positions[position].EjectDriver ( force ) ) { return false; } UpdateDrivers ( -1 ); return true; } /* ================ rvVehicle::EjectAllDrivers ================ */ void rvVehicle::EjectAllDrivers( bool force ) { for( int ix = positions.Num() - 1; ix >= 0; --ix ) { if( !GetPosition(ix)->GetDriver() ) { continue; } GetPosition(ix)->GetDriver()->ProcessEvent( &AI_ExitVehicle, force ); } } /* ============ rvVehicle::Damage ============ */ void rvVehicle::Damage ( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, int location ) { int damage; if ( !fl.takedamage ) { return; } const idDict *damageDef = gameLocal.FindEntityDefDict( damageDefName, false ); if ( !damageDef ) { gameLocal.Warning( "Unknown damageDef '%s'", damageDefName ); return; } if ( damageDef->GetBool( "ignore_vehicles", "0" ) ) { return; } damageDef->GetInt( "damage", "20", damage ); damage *= damageScale; int save = HasDrivers() ? shieldHealth.GetCurrentValue ( gameLocal.time ) : 0; // If one of the drivers is the player, do the player HUD effects for ( int i = 0; i < positions.Num(); i++ ) { rvVehiclePosition & pos = positions[ i ]; if ( pos.mDriver.IsValid() && pos.mDriver->IsType( idPlayer::GetClassType() ) ) { idPlayer & driver = static_cast< idPlayer & >( *pos.mDriver.GetEntity() ); if ( driver.GetHud() ) { driver.GetHud()->HandleNamedEvent( "vehicleHit" ); } if ( !stricmp( damageDef->GetString( "filter" ), "electrical" ) ) { driver.GetHud()->HandleNamedEvent( "electricWarningOn" ); PostEventMS( &EV_HUDShockWarningOff, spawnArgs.GetInt( "hud_shock_warning_time", "2500" ) ); if ( damage >= spawnArgs.GetInt( "electric_stall_damage", "20" ) ) { rvClientCrawlEffect* effect; effect = new rvClientCrawlEffect ( gameLocal.GetEffect( spawnArgs , "fx_electrical_stall" ), this, SEC2MS( spawnArgs.GetFloat ( "hud_shock_warning_time", "2.5" ) ) ); effect->Play ( gameLocal.time, false ); if ( renderEntity.gui[ 0 ] ) { renderEntity.gui[ 0 ]->HandleNamedEvent( "shock_stall" ); } if ( renderEntity.gui[ 1 ] ) { renderEntity.gui[ 1 ]->HandleNamedEvent( "shock_stall" ); } vfl.stalled = true; PostEventMS( &EV_StalledRestart, spawnArgs.GetInt( "electric_stall_delay", "3500" ), save, damage ); } } } } shieldHitTime = gameLocal.time; // Shields off when there is no controller if ( !damageDef->GetBool( "noShields", "0" ) ) { if ( save > 0 ) { int shield; save = Min( save, damage ); damage -= save; shield = shieldHealth.GetCurrentValue ( gameLocal.time ) - save; shieldHealth.Init ( gameLocal.time, 0, shield, shield ); // always stop the sound because we may be playing the recharge just now. StopSound ( SND_CHANNEL_BODY2, false ); // Looping warning sound for shield if ( shield <= 0 && !vfl.stalled ) { StartSound ( "snd_shieldWarning", SND_CHANNEL_BODY2, 0, false, NULL ); } } } // God Mode? if ( vfl.godmode && !damageDef->GetBool( "noGod" ) ) { godModeDamage += damage; damage = 0; } if ( !damage ) { return; } // Static on the gui when hit if ( renderEntity.gui[0] && gameLocal.random.RandomFloat() < damageStaticChance ) { renderEntity.gui[0]->HandleNamedEvent ( "shot_static" ); } // Play low health warning on transition to low health value if ( health >= healthLow && health - damage < healthLow ) { StartSound ( "snd_voiceLowHealth", SND_CHANNEL_VOICE, 0, false, NULL ); } // RAVEN BEGIN // MCG - added damage over time if ( !inDamageEvent ) { if ( damageDef->GetFloat( "dot_duration" ) ) { int endTime; if ( damageDef->GetFloat( "dot_duration" ) == -1 ) { endTime = -1; } else { endTime = gameLocal.GetTime() + SEC2MS(damageDef->GetFloat( "dot_duration" )); } int interval = SEC2MS(damageDef->GetFloat( "dot_interval", "0" )); if ( endTime == -1 || gameLocal.GetTime() + interval <= endTime ) {//post it again PostEventMS( &EV_DamageOverTime, interval, endTime, interval, inflictor, attacker, dir, damageDefName, damageScale, location ); } if ( damageDef->GetString( "fx_dot", NULL ) ) { ProcessEvent( &EV_DamageOverTimeEffect, endTime, interval, damageDefName ); } if ( damageDef->GetString( "snd_dot_start", NULL ) ) { StartSound ( "snd_dot_start", SND_CHANNEL_ANY, 0, false, NULL ); } } } // RAVEN END health -= damage; if ( health < 1 && damageDef->GetBool( "nonLethal" ) ) { health = 1; } if ( health <= 0 ) { // keep the driver from being killed if we're forcing undying state if ( g_forceUndying.GetBool() && HasDrivers() ) { idActor * driver = NULL; for ( int i = 0; i < positions.Num(); i++ ) { idActor * currentDriver = positions[ i ].GetDriver(); if ( currentDriver && currentDriver->IsType( idPlayer::GetClassType() ) ) { driver = currentDriver; break; } } if ( driver ) { health = 1; Pain( inflictor, attacker, damage, dir, 0 ); return; } } if ( health < -999 ) { health = -999; } Killed( inflictor, attacker, damage, dir, location ); } else { Pain( inflictor, attacker, damage, dir, 0 ); } } /* ================ rvVehicle::Killed ================ */ void rvVehicle::Killed ( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { int i; lockWarningTime = 0; hazardWarningTime = 0; vfl.locked = false; vfl.dead = true; // Try removing the vehicle from all lists it could be part of. aiManager.RemoveTeammate ( this ); // Remove all drivers from the vehicle and kill them for ( i = 0; i < positions.Num(); i ++ ) { idActor* driver = positions[i].GetDriver(); if ( !driver ) { continue; } // Dump the driver out of the vehicle and kill them driver->health = 0; driver->ProcessEvent ( &AI_ExitVehicle, true ); driver->Killed( inflictor, attacker, damage, dir, location ); } OnDeath(); CheckDeathObjectives(); if ( spawnArgs.GetBool( "remove_on_death", "1" ) ) { StartSound ( "snd_death", SND_CHANNEL_ANY, 0, false, NULL ); if ( spawnArgs.GetBool( "orient_death_fx", "0" ) ) { gameLocal.PlayEffect ( spawnArgs, "fx_death", GetPhysics()->GetAbsBounds().GetCenter(), GetPhysics()->GetAxis() ); } else { gameLocal.PlayEffect ( spawnArgs, "fx_death", GetPhysics()->GetAbsBounds().GetCenter(), idVec3(0,0,1).ToMat3() ); } Hide ( ); PostEventMS( &EV_Remove, 0 ); } } /* ================ rvVehicle::AddDamageEffect ================ */ void rvVehicle::AddDamageEffect ( const trace_t &collision, const idVec3 &velocity, const char *damageDefName, idEntity* inflictor ) { // If there are still shields remaining then play a shield effect at the impact point if ( HasDrivers() && shieldHealth.GetCurrentValue ( gameLocal.time ) > 0 ) { jointHandle_t joint = animator.GetJointHandle( spawnArgs.GetString( "fx_shield_joint", "" ) ); idVec3 dir; dir = collision.c.point - GetPhysics()->GetCenterMass (); dir.NormalizeFast ( ); if ( INVALID_JOINT == joint ) { PlayEffect ( "fx_shield", collision.c.point, dir.ToMat3(), false, vec3_origin, true ); } else { PlayEffect ( "fx_shield", joint, false, vec3_origin, true ); } } } /* ================ rvVehicle::Collide ================ */ bool rvVehicle::Collide( const trace_t &collision, const idVec3 &velocity ) { idVec3 dir; float speed; float collisionSelfDamage = 0.0f; dir = velocity; speed = dir.Normalize(); // No collision effect when hitting a no impact surfac // if ( collision.c.material && collision.c.material->GetSurfaceFlags() & SURF_NOIMPACT ) { // return false; // } if ( vfl.dead ) { StartSound ( "snd_death", SND_CHANNEL_ANY, 0, false, NULL ); if ( spawnArgs.GetBool( "orient_death_fx", "0" ) ) { gameLocal.PlayEffect ( spawnArgs, "fx_death", GetPhysics()->GetAbsBounds().GetCenter(), GetPhysics()->GetAxis() ); } else { gameLocal.PlayEffect ( spawnArgs, "fx_death", GetPhysics()->GetAbsBounds().GetCenter(), idVec3(0,0,1).ToMat3() ); } Hide ( ); PostEventMS( &EV_Remove, 0 ); } if ( !alwaysImpactDamage ) { if ( speed < crashSpeedSmall ) { return false; } } // When colliding with the world play a collision effect if ( collision.c.entityNum == ENTITYNUM_WORLD ) { // TODO: MAterial types if ( !crashEffect ) { crashEffect = gameLocal.PlayEffect ( gameLocal.GetEffect ( spawnArgs, "fx_crash" ), collision.c.point, collision.c.normal.ToMat3(), true ); } if ( crashEffect ) { crashEffect->SetOrigin ( collision.c.point ); crashEffect->SetAxis ( collision.c.normal.ToMat3() ); crashEffect->Attenuate ( idMath::ClampFloat ( 0.01f, 1.0f, speed / (float)crashSpeedLarge ) ); } } idStr collDmgDef = "damage_gev_collision_self"; if ( gameLocal.time > crashNextSound ) { float dot = dir * collision.c.normal; if( dot < -0.5f ) { // Crash impact sounds if ( speed > crashSpeedLarge ) { collisionSelfDamage += 50.0f; StartSound ( "snd_crash_large", SND_CHANNEL_ANY, 0, false, NULL ); crashNextSound = gameLocal.time + VEHICLE_CRASH_DELAY; // damage = true; } else if ( speed > crashSpeedMedium ) { collisionSelfDamage += 25.0f; StartSound ( "snd_crash_medium", SND_CHANNEL_ANY, 0, false, NULL ); crashNextSound = gameLocal.time + VEHICLE_CRASH_DELAY; // damage = true; } else if ( speed > crashSpeedSmall ) { StartSound ( "snd_crash_small", SND_CHANNEL_ANY, 0, false, NULL ); crashNextSound = gameLocal.time + VEHICLE_CRASH_DELAY; } } } crashTime = gameLocal.time; float vel = GetPhysics()->GetLinearVelocity().Length(); if ( vel > crashVelocitySmall && crashDamage.Length ( ) ) { idEntity* ent = gameLocal.entities[ collision.c.entityNum ]; if ( ent ) { float f = vel > crashVelocityLarge ? 1.0f : idMath::Sqrt( vel - crashVelocityMedium ) * ( 1.0f / idMath::Sqrt( crashVelocityLarge - crashVelocityMedium ) ); // Now hurt him if ( !( team != gameLocal.GetLocalPlayer()->team && ent->IsType( idPlayer::GetClassType() ) ) ) { const idDict* damageDef = gameLocal.FindEntityDefDict ( crashDamage, false ); //MCG NOTE: This used to call vehicleController.GetDriver(), which was always NULL... idActor* theDriver = positions[0].GetDriver(); if ( ent->fl.takedamage && ent->health >= 0 && !ent->spawnArgs.GetBool( "ignore_vehicle_damage" ) && (vel > 100.0f || !ent->IsType(idPlayer::GetClassType())) ) { //MCG NOTE: now that theDriver is being set correctly, this damage will get credited to the driver (as the attacker), unlike before float dScale = f * ent->spawnArgs.GetFloat( "vehicle_damage_scale", "1" ); ent->Damage( this, theDriver, dir, crashDamage, dScale, INVALID_JOINT ); if ( vel > 100.0f ) { //NOTE: we PURPOSELY override this here, so you don't take damage from slamming into damageable things, only invulnerable things... collisionSelfDamage = ent->spawnArgs.GetFloat( "vehicle_impact_damage", "0" ); collDmgDef = "damage_gev_collision"; } } // ApplyImpulse doesn't like it when you give it a null driver // MCG: okay, now ApplyImpulse actually is getting called, // but I'm only going to allow it on idMoveables since ApplyImpulse is // rejected by idActors if it comes from an idActor and this code was // never being called before. if ( theDriver && damageDef && !ent->spawnArgs.GetBool("ignore_vehicle_push") && ent->IsType( idMoveable::GetClassType() ) ) { float push = damageDef->GetFloat( "push" ) * speed / idMath::ClampFloat ( 0.01f, 1.0f, speed / (float)crashSpeedLarge ); idVec3 impulse = -push * f * collision.c.normal; impulse[2] = push * f * 0.75f; // Send him flying ent->ApplyImpulse( theDriver, collision.c.id, collision.c.point, impulse ); if ( g_debugVehicle.GetInteger() ) { gameRenderWorld->DebugArrow ( colorGreen, collision.c.point, collision.c.point + impulse, 3, 10000 ); } } } } } if ( collisionSelfDamage ) { idVec3 dmgDir = GetPhysics()->GetLinearVelocity()*-1.0f; Damage( this, this, dmgDir, collDmgDef.c_str(), collisionSelfDamage, 0 ); } return false; } /* =============== rvVehicle::Give =============== */ bool rvVehicle::Give( const char *statname, const char *value ) { if ( !idStr::Icmp( statname, "health" ) ) { if ( health >= healthMax ) { return false; } health = Min ( atoi( value ) + health, healthMax ); } return true; } /* ================ rvVehicle::AutoRight ================ */ void rvVehicle::AutoRight( idEntity* activator ) { if( autoRight ) { return; } autoRight = true; /* idVec3 vec = renderEntity.origin - activator->renderEntity.origin; vec.Normalize(); float dot = DotProduct( vec, renderEntity.axis[ 1 ] ); if( dot < 0 ) { autoRightDir = -1.0f; } else { autoRightDir = 1.0f; } */ } /* ================ rvVehicle::GetPositionIndex ================ */ int rvVehicle::GetPositionIndex ( const rvVehiclePosition* position ) const { int i; for ( i = positions.Num() - 1; i >= 0; i -- ) { if ( &positions[i] == position ) { return i; } } return -1; } /* ================ rvVehicle::UpdateHUD ================ */ void rvVehicle::UpdateHUD ( int position, idUserInterface* gui ) { // VEH_FIXME: godmode ? /* if ( mController && mController->GetDriver() && mController->GetDriver()->IsType ( idPlayer::Type ) ) { idPlayer* player = static_cast(mController->GetDriver()); gui->State()->SetInt ( "vehicle_god", player->godmode && g_showGodDamage->integer ); gui->State()->SetInt ( "vehicle_god_damage", godModeDamage ); } else { gui->State()->SetInt ( "vehicle_god", 0 ); } */ gui->SetStateFloat( "vehicle_health", health / spawnArgs.GetFloat( "health", "1" ) ); gui->SetStateInt ( "vehicle_armor", 0 ); gui->SetStateInt ( "vehicle_position", position ); gui->SetStateFloat ( "vehicle_shield", (float)shieldHealth.GetCurrentValue(gameLocal.time) / (float)shieldMaxHealth ); gui->SetStateInt ( "vehicle_haz", hazardWarningTime != 0 ); gui->SetStateInt ( "vehicle_locked", IsLocked ( ) ); // Update position specific information positions[position].UpdateHUD ( gui ); } /* ================ rvVehicle::UpdateCursorGUI ================ */ void rvVehicle::UpdateCursorGUI ( int position, idUserInterface* gui ) { positions[position].UpdateCursorGUI ( gui ); } /* ================ rvVehicle::DrawHUD ================ */ void rvVehicle::DrawHUD ( int position ) { if ( !hud || gameLocal.GetLocalPlayer()->IsObjectiveUp() || gameLocal.GetLocalPlayer()->objectiveSystemOpen ) { return; } UpdateHUD ( position, hud ); hud->Redraw ( gameLocal.time ); } /* ================== rvVehicle::IssueHazardWarning ================== */ void rvVehicle::IssueHazardWarning ( void ) { if ( !hazardWarningTime ) { StartSound ( "snd_voiceHazard", SND_CHANNEL_VOICE, 0, false, NULL ); if ( renderEntity.gui[0] ) { renderEntity.gui[0]->HandleNamedEvent ( "warning_hazard" ); } } hazardWarningTime = gameLocal.time; } /* ================== rvVehicle::IssueHazardWarning ================== */ void rvVehicle::IssueLockedWarning ( void ) { if ( gameLocal.time > lockWarningTime + VEHICLE_LOCK_TIMEOUT ) { StartSound ( "snd_voiceNoExit", SND_CHANNEL_VOICE, 0, false, NULL ); if ( renderEntity.gui[0] ) { renderEntity.gui[0]->HandleNamedEvent ( "warning_locked" ); } lockWarningTime = gameLocal.time; } } /* ================== rvVehicle::Hide ================== */ void rvVehicle::Hide ( void ) { idActor::Hide ( ); GetPhysics()->SetContents( 0 ); GetPhysics()->GetClipModel()->Unlink(); } /* ================== rvVehicle::Show ================== */ void rvVehicle::Show ( void ) { idActor::Show ( ); GetPhysics()->SetContents ( CONTENTS_BODY ); GetPhysics()->GetClipModel()->Link(); } /* ================== rvVehicle::Lock ================== */ void rvVehicle::Lock( void ) { Event_Lock( true ); } /* ================== rvVehicle::Unlock ================== */ void rvVehicle::Unlock( void ) { Event_Lock( false ); } /* ================== rvVehicle::GuidedProjectileLocked ================== */ void rvVehicle::GuidedProjectileIncoming( idGuidedProjectile * projectile ) { if ( projectile ) { incomingProjectiles.Insert( projectile ); } } /* ================== rvVehicle::UpdateIncomingProjectiles ================== */ void rvVehicle::UpdateIncomingProjectiles( void ) { idGuidedProjectile * proj = NULL; float dist = 0.0f; for( int i = incomingProjectiles.Num() - 1; i >= 0; i-- ) { if ( !incomingProjectiles[ i ].IsValid() ) { incomingProjectiles.RemoveIndex( i ); continue; } if ( proj ) { float d = ( incomingProjectiles[ i ]->GetPhysics()->GetOrigin() - this->GetPhysics()->GetOrigin() ).LengthSqr(); if ( dist > d ) { proj = incomingProjectiles[ i ]; dist = d; } } else { proj = incomingProjectiles[ i ]; dist = ( incomingProjectiles[ i ]->GetPhysics()->GetOrigin() - this->GetPhysics()->GetOrigin() ).LengthSqr(); } } if ( proj ) { idVec3 localDir; int length; GetPosition( 0 )->mEyeAxis.ProjectVector( proj->GetPhysics()->GetOrigin() - GetOrigin(), localDir ); GetHud()->SetStateFloat ( "missiledir", localDir.ToAngles()[YAW] ); idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle ); if ( !vfl.missileWarningOn ) { if ( GetHud() ) { GetHud()->HandleNamedEvent( "missileThreatUp" ); } if ( emitter ) { StartSound( "snd_incomingProjectile", SND_CHANNEL_BODY3, 0, false, &length ); } vfl.missileWarningOn = true; } if ( emitter ) { //float lerp = 1.0f - idMath::ClampFloat( 0.0f, 1.0f, ( dist / 100000.0f ) ); //refSound.parms.volume = lerp * 0.6f + 0.5f; //refSound.parms.frequencyShift = lerp * 0.4f + 0.8f; //emitter->ModifySound ( SND_CHANNEL_ANY, &refSound.parms ); } } else if ( vfl.missileWarningOn ) { if ( GetHud() ) { GetHud()->HandleNamedEvent( "missileThreatDown" ); } StopSound( SND_CHANNEL_BODY3, false ); vfl.missileWarningOn = false; } } /* ================== rvVehicle::Event_Lock ================== */ void rvVehicle::Event_Lock ( bool lock ) { vfl.locked = lock; } /* ================== rvVehicle::Event_IsLocked ================== */ void rvVehicle::Event_IsLocked ( void ) { idThread::ReturnFloat( (float)IsLocked() ); } /* ================== rvVehicle::Event_EnableWeapon ================== */ void rvVehicle::Event_EnableWeapon ( void ) { vfl.disableWeapons = false; } /* ================== rvVehicle::Event_DisableWeapon ================== */ void rvVehicle::Event_DisableWeapon ( void ) { vfl.disableWeapons = true; } /* ================== rvVehicle::Event_EnableMovement ================== */ void rvVehicle::Event_EnableMovement( void ) { vfl.disableMovement = false; } /* ================== rvVehicle::Event_DisableMovement ================== */ void rvVehicle::Event_DisableMovement ( void ) { vfl.disableMovement = true; } /* ================== rvVehicle::Event_EnableClip ================== */ void rvVehicle::Event_EnableClip( void ) { GetPhysics()->SetContents( cachedContents ); } /* ================== rvVehicle::Event_DisableClip ================== */ void rvVehicle::Event_DisableClip( void ) { cachedContents = GetPhysics()->GetContents(); GetPhysics()->SetContents( 0 ); } /* ================== rvVehicle::Event_Activate ================== */ void rvVehicle::Event_Activate( idEntity* activator ) { RemoveNullTargets(); if( !targets.Num() && activator && activator->IsType(idPlayer::GetClassType()) ) { static_cast( activator )->EnterVehicle( this ); } } /* ================== rvVehicle::Event_LaunchProjectiles ================== */ void rvVehicle::Event_LaunchProjectiles( const idList* parms ) { int pos = -1; assert( parms && parms->Num() ); sscanf( (*parms)[0].c_str(), "%d", &pos ); GetPosition( pos )->FireWeapon(); } /* ================== rvVehicle::Event_SetScript ================== */ void rvVehicle::Event_SetScript( const char* scriptName, const char* funcName ) { if ( !funcName || !funcName[0] ) { return; } // Set the associated script if ( !idStr::Icmp ( scriptName, "enter" ) ) { funcs.enter.Init( funcName ); } else if ( !idStr::Icmp ( scriptName, "exit" ) ) { funcs.exit.Init( funcName ); } else { gameLocal.Warning ( "unknown script '%s' specified on vehicle '%s'", scriptName, name.c_str() ); } } /* ================ rvVehicle::Event_SetHealth ================ */ void rvVehicle::Event_SetHealth ( float health ) { this->health = health; } /* ================ rvVehicle::Event_HUDShockWarningOff ================ */ void rvVehicle::Event_HUDShockWarningOff ( ) { if ( GetHud() ) { GetHud()->HandleNamedEvent( "electricWarningOff" ); } } /* ================ rvVehicle::Event_StalledRestart ================ */ void rvVehicle::Event_StalledRestart ( float shield, float damage ) { vfl.stalled = false; if ( renderEntity.gui[ 0 ] ) { renderEntity.gui[ 0 ]->HandleNamedEvent( "shock_stall_restart" ); } if ( renderEntity.gui[ 1 ] ) { renderEntity.gui[ 1 ]->HandleNamedEvent( "shock_stall_restart" ); } // Looping warning sound for shield if ( shield <= 0 ) { StopSound ( SND_CHANNEL_BODY2, false ); StartSound ( "snd_shieldWarning", SND_CHANNEL_BODY2, 0, false, NULL ); } // Play low health warning on transition to low health value if ( health >= healthLow && health - damage < healthLow ) { StartSound ( "snd_voiceLowHealth", SND_CHANNEL_VOICE, 0, false, NULL ); } } /* ================ rvVehicle::Event_GetViewAngles ================ */ void rvVehicle::Event_GetViewAngles ( ) { idThread::ReturnVector( GetPosition( 0)->GetEyeAxis()[0]); } /* =============================================================================== States =============================================================================== */ CLASS_STATES_DECLARATION ( rvVehicle ) STATE ( "Wait_Driver", rvVehicle::State_Wait_Driver ) END_CLASS_STATES /* ================ rvVehicle::State_Wait_Driver ================ */ stateResult_t rvVehicle::State_Wait_Driver ( int blendFrames ) { if ( !vfl.driver || vfl.stalled ) { return SRESULT_WAIT; } return SRESULT_DONE; }