// 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 "VehicleControl.h" #include "Transport.h" #include "TransportComponents.h" #include "Vehicle_RigidBody.h" #include "../ContentMask.h" #include "../script/Script_Helper.h" #include "../script/Script_ScriptObject.h" #include "../../decllib/DeclSurfaceType.h" #include "../InputMode.h" #include "../rules/GameRules.h" idCVar g_vehicleSteerKeyScale( "g_vehicleSteerKeyScale", "1", CVAR_GAME | CVAR_ARCHIVE | CVAR_FLOAT | CVAR_PROFILE, "The scale of the wheeled vehicle steering keys - 1 is standard, 2 is twice as fast, etc" ); /* =============================================================================== sdVehicleControlBase =============================================================================== */ /* ================ sdVehicleControlBase::Init ================ */ void sdVehicleControlBase::Init( sdTransport* transport ) { owner = transport; } /* =============================================================================== sdVehicleScriptControl =============================================================================== */ /* ================ sdVehicleScriptControl::~sdVehicleScriptControl ================ */ sdVehicleScriptControl::~sdVehicleScriptControl() { if ( inputThread != NULL ) { gameLocal.program->FreeThread( inputThread ); inputThread = NULL; } } /* ================ sdVehicleScriptControl::Init ================ */ void sdVehicleScriptControl::Init( sdTransport* transport ) { sdVehicleControlBase::Init( transport ); inputThreadFunc = transport->GetScriptObject()->GetFunction( "InputThread" ); if ( inputThreadFunc ) { inputThread = gameLocal.program->CreateThread(); inputThread->ManualControl(); inputThread->ManualDelete(); } inputMode = 0; idStr modeString; transport->spawnArgs.GetString( "input_mode", "", modeString ); if ( !modeString.Icmp( "car" ) ) { inputMode = 1; } else if ( !modeString.Icmp( "helicopter" ) ) { inputMode = 2; } else if ( !modeString.Icmp( "hovertank" ) ) { inputMode = 3; } } /* ================ sdVehicleScriptControl::Update ================ */ void sdVehicleScriptControl::Update() { if ( inputThread ) { inputThread->CallFunction( owner->GetScriptObject(), inputThreadFunc ); inputThread->Execute(); } } /* ================ sdVehicleScriptControl::OnKeyMove ================ */ bool sdVehicleScriptControl::OnKeyMove( char forward, char right, char up, usercmd_t& cmd ) { return false; } /* ================ sdVehicleScriptControl::OnControllerMove ================ */ void sdVehicleScriptControl::OnControllerMove( bool doGameCallback, const int numControllers, const int* controllerNumbers, const float** controllerAxis, idVec3& viewAngles, usercmd_t& cmd ) { // run the input for each controller for ( int i = 0; i < numControllers; i++ ) { int num = controllerNumbers[ i ]; switch ( inputMode ) { case 1: // car sdInputModeCar::ControllerMove( doGameCallback, num, controllerAxis[ i ], viewAngles, cmd ); break; case 2: // helicopter sdInputModeHeli::ControllerMove( doGameCallback, num, controllerAxis[ i ], viewAngles, cmd ); break; case 3: // hovertank sdInputModeHovertank::ControllerMove( doGameCallback, num, controllerAxis[ i ], viewAngles, cmd ); break; default: case 0: // player sdInputModePlayer::ControllerMove( doGameCallback, num, controllerAxis[ i ], viewAngles, cmd ); break; } } } /* =============================================================================== sdVehicleControl =============================================================================== */ /* ================ sdVehicleControl::Init ================ */ void sdVehicleControl::Init( sdTransport* transport ) { sdVehicleControlBase::Init( transport ); state = CS_SHUTDOWN; newState = CS_SHUTDOWN; newStateTime = 0; overDriveSoundEndTime = 0; // 0 -> not playing overDrivePitchStart = transport->spawnArgs.GetFloat( "overdrive_pitch_low", "1" ); overDrivePitchEnd = transport->spawnArgs.GetFloat( "overdrive_pitch_high", "1" ); overDrivePitchStartSpeed = transport->spawnArgs.GetFloat( "overdrive_speed_low", "0" ); overDrivePitchEndSpeed = transport->spawnArgs.GetFloat( "overdrive_speed_high", "0" ); drownHeight = transport->spawnArgs.GetFloat( "drown_height", "0.5" ); } /* ================ sdVehicleControl::Update ================ */ void sdVehicleControl::Update() { assert( owner ); assert( input ); SetupInput(); RunStateMachine(); HandlePhysics(); } /* ================ sdVehicleControl::HandlePhysics ================ */ void sdVehicleControl::HandlePhysics() { owner->UpdateEngine( !EngineRunning() ); } /* ================ sdVehicleControl::RunStateMachine ================ */ void sdVehicleControl::RunStateMachine() { if( newStateTime ) { if( gameLocal.time > newStateTime ) { state = newState; newStateTime = 0; HandleStateChange( newState ); } else { return; } } // only gets here if there is no state change to do assert( !newStateTime ); idPlayer* driver = owner->GetPositionManager().FindDriver(); if ( state == CS_POWERED ) { if ( !driver || ( owner->GetHealth() <= 0 ) || owner->IsEMPed() ) { // owner->StartSound( "snd_shutdown", SND_BODY, 0, NULL ); newStateTime = gameLocal.time + 500; newState = CS_SHUTDOWN; } } else if ( state == CS_SHUTDOWN ) { if ( driver && ( owner->GetHealth() > 0 ) && !owner->IsEMPed() ) { // owner->StartSound( "snd_startup", SND_BODY, 0, NULL ); newStateTime = gameLocal.time + 500; newState = CS_POWERED; } } } /* ================ sdVehicleControl::HandleStateChange ================ */ void sdVehicleControl::HandleStateChange( controlState_t s ) { if( s == CS_SHUTDOWN ) { owner->GetPhysics()->SetComeToRest( true ); owner->SetLightsEnabled( 0, false ); } else if( s == CS_POWERED ) { owner->SetLightsEnabled( 0, true ); } } /* ================ sdVehicleControl::EngineRunning ================ */ bool sdVehicleControl::EngineRunning() { if ( owner->IsAmphibious() ) { if ( owner->IsFlipped() ) { return false; } } else if ( owner->GetPhysics()->InWater() > drownHeight ) { return false; } return ( state == CS_POWERED ) && ( newState == CS_POWERED ) && !owner->IsEMPed() && !isImmobilized; } /* ================ sdVehicleControl::UpdateOverdriveSound ================ */ void sdVehicleControl::UpdateOverdriveSound( bool thrusting ) { idPlayer* driver = owner->GetPositionManager().FindDriver(); if ( thrusting ) { if ( gameLocal.isNewFrame ) { if ( overDriveSoundEndTime >= 0 && gameLocal.time > overDriveSoundEndTime ) { // freshly start the sound overDriveSoundEndTime = -1; owner->StartSound( "snd_overdrive", SND_VEHICLE_OVERDRIVE, 0, NULL ); // fade sound in from nothing owner->FadeSound( SND_VEHICLE_OVERDRIVE, -60.0f, 0.0f ); owner->FadeSound( SND_VEHICLE_OVERDRIVE, 0.0f, 0.01f ); } if ( gameLocal.time < overDriveSoundEndTime ) { // fade sound in overDriveSoundEndTime = -1; owner->FadeSound( SND_VEHICLE_OVERDRIVE, 0.0f, 0.01f ); } } if( driver != NULL && driver == gameLocal.GetLocalPlayer() ) { gameLocal.SetGUIFloat( GUI_GLOBALS_HANDLE, "vehicles.overDriveFraction", 1.0f ); } } else { if ( gameLocal.isNewFrame ) { if ( overDriveSoundEndTime < 0 ) { owner->StartSound( "snd_overdrive_stop", SND_VEHICLE_DRIVE5, 0, NULL ); owner->FadeSound( SND_VEHICLE_DRIVE5, -60.0f, 1.0f ); owner->FadeSound( SND_VEHICLE_OVERDRIVE, -60.0f, 1.0f ); overDriveSoundEndTime = gameLocal.time + 300; } } if( driver != NULL && driver == gameLocal.GetLocalPlayer() ) { gameLocal.SetGUIFloat( GUI_GLOBALS_HANDLE, "vehicles.overDriveFraction", 0.5f ); } } if ( overDrivePitchEndSpeed != overDrivePitchStartSpeed && ( overDriveSoundEndTime == -1 || gameLocal.time < overDriveSoundEndTime ) ) { float speed = idMath::Fabs( owner->GetPhysics()->GetLinearVelocity() * owner->GetPhysics()->GetAxis()[ 0 ] ); speed = UPSToKPH( speed ); float pitch = idMath::ClampFloat( 0.0f, 1.0f, ( speed - overDrivePitchStartSpeed ) / ( overDrivePitchEndSpeed - overDrivePitchStartSpeed ) ); pitch = idMath::Sqrt( pitch ) * ( overDrivePitchEnd - overDrivePitchStart ) + overDrivePitchStart; owner->SetChannelPitchShift( SND_VEHICLE_OVERDRIVE, pitch ); } } /* =============================================================================== sdDesecratorControl =============================================================================== */ /* ================ sdDesecratorControl::Init ================ */ void sdDesecratorControl::Init( sdTransport* transport ) { sdVehicleControl::Init( transport ); wantsSiegeMode = true; inSiegeMode = false; lastGroundEffectsTime = 0; } /* ================ sdDesecratorControl::SetupInput ================ */ void sdDesecratorControl::SetupInput() { idPlayer* driver = owner->GetPositionManager().FindDriver(); bool braking = false; idVec3 directions = vec3_origin; float strafing = 0.0f; input->Clear(); if ( driver != NULL && EngineRunning() ) { input->SetPlayer( driver ); directions.Set( input->GetForward(), input->GetRight(), input->GetUp() ); if ( driver->usercmd.buttons.btn.leanLeft ) { strafing -= 1.0f; } if ( driver->usercmd.buttons.btn.leanRight ) { strafing += 1.0f; } if ( directions.z > 0.0f && wantsSiegeMode ) { wantsSiegeMode = false; } else if ( directions.z < 0.0f && !wantsSiegeMode ) { wantsSiegeMode = true; } input->SetCollective( strafing ); input->SetSteerAngle( directions.y ); } else { braking = true; } bool handbraking = wantsSiegeMode; bool canThrust = false; if ( driver != NULL && wantsSiegeMode == false ) { if ( fabs( directions.x ) > 0.0f || strafing != 0.0f ) { canThrust = driver->usercmd.buttons.btn.sprint; } } input->SetForce( 1.0f ); bool thrusting = canThrust; if ( thrusting && owner->IsInPlayzone() ) { // increase the force multiplier input->SetForce( 1.3f ); } UpdateOverdriveSound( thrusting ); input->SetBraking( braking ); input->SetHandBraking( handbraking ); } /* ================ sdDesecratorControl::OnPlayerEntered ================ */ void sdDesecratorControl::OnPlayerEntered( idPlayer* player, int position, int oldPosition ) { idPlayer* driver = owner->GetPositionManager().FindDriver(); if ( driver && driver == player ) { wantsSiegeMode = false; } if ( oldPosition == 0 && position != 0 ) { wantsSiegeMode = true; } } /* ================ sdDesecratorControl::OnPlayerExited ================ */ void sdDesecratorControl::OnPlayerExited( idPlayer* player, int position ) { // it was the driver that left if ( position == 0 ) { wantsSiegeMode = true; } } /* ================ sdDesecratorControl::OnKeyMove ================ */ bool sdDesecratorControl::OnKeyMove( char forward, char right, char up, usercmd_t& cmd ) { return false; } /* ================ sdDesecratorControl::OnControllerMove ================ */ void sdDesecratorControl::OnControllerMove( bool doGameCallback, const int numControllers, const int* controllerNumbers, const float** controllerAxis, idVec3& viewAngles, usercmd_t& cmd ) { // run the input for each controller for ( int i = 0; i < numControllers; i++ ) { int num = controllerNumbers[ i ]; sdInputModeHovertank::ControllerMove( doGameCallback, num, controllerAxis[ i ], viewAngles, cmd ); } } void sdDesecratorControl::UpdateEffects() { idVec3 absMins = owner->GetPhysics()->GetAbsBounds()[ 0 ]; idVec3 absMaxs = owner->GetPhysics()->GetAbsBounds()[ 1 ]; idVec3 traceOrg = ( absMins + absMaxs ) * 0.5f; idVec3 traceEnd = traceOrg; traceEnd.z -= 400.0f; trace_t traceObject; gameLocal.clip.TracePoint( CLIP_DEBUG_PARMS traceObject, traceOrg, traceEnd, MASK_SOLID | CONTENTS_WATER | MASK_OPAQUE, owner ); traceEnd = traceObject.endpos; #if 0 if ( traceObject.fraction < 1.0f ) { height = traceOrg.z - traceEnd.z; } else { height = -1.0f; } #endif bool isEmpty = owner->GetPositionManager().IsEmpty(); if ( gameLocal.time >= ( lastGroundEffectsTime + 200 ) && !isEmpty && /*groundEffects && */traceObject.fraction < 1.0f ) { const char* surfaceTypeName = NULL; if ( traceObject.c.surfaceType ) { surfaceTypeName = traceObject.c.surfaceType->GetName(); } owner->PlayEffect( "fx_groundeffect", colorWhite.ToVec3(), surfaceTypeName, traceObject.endpos, traceObject.c.normal.ToMat3(), 0 ); lastGroundEffectsTime = gameLocal.time; } } void sdDesecratorControl::RunStateMachine() { controlState_t oldNewState = newState; sdVehicleControl::RunStateMachine(); if ( gameLocal.isNewFrame ) { UpdateEffects(); } if ( state == CS_POWERED && newState == CS_SHUTDOWN && oldNewState != CS_SHUTDOWN ) { owner->StartSound( "snd_shutdown", SND_BODY, 0, NULL ); } if ( state == CS_SHUTDOWN && newState == CS_POWERED && oldNewState != CS_POWERED ) { owner->StartSound( "snd_startup", SND_BODY, 0, NULL ); } } /* =============================================================================== sdWheeledVehicleControl =============================================================================== */ /* ================ sdWheeledVehicleControl::Init ================ */ void sdWheeledVehicleControl::Init( sdTransport* transport ) { sdVehicleControl::Init( transport ); oldSpeedKPH = 0.0f; nextBrakeSoundTime = 0; const char* tableName = transport->spawnArgs.GetString( "table_gearforces" ); tableName = transport->spawnArgs.GetString( va( "table_gearforces%s", gameLocal.rules->GetKeySuffix() ), tableName ); gearForceTable = gameLocal.declTableType[ tableName ]; if ( !gearForceTable ) { gameLocal.Error( "sdWheeledVehicleControl::Init - gear force table \'%s\' not found", tableName ); } tableName = transport->spawnArgs.GetString( "table_gearspeeds" ); tableName = transport->spawnArgs.GetString( va( "table_gearspeeds%s", gameLocal.rules->GetKeySuffix() ), tableName ); gearSpeedTable = gameLocal.declTableType[ tableName ]; if ( !gearSpeedTable ) { gameLocal.Error( "sdWheeledVehicleControl::Init - gear speed table \'%s\' not found", tableName ); } powerCurveScale = transport->spawnArgs.GetFloat( "power_curve_scale", "0" ); overDriveFactor = transport->spawnArgs.GetFloat( "overdrive_factor" ); steeringAngle = transport->spawnArgs.GetFloat( "steering_angle" ); forwardSteerSpeed = transport->spawnArgs.GetFloat( "simplesteer_forward_speed", "2.0" ); reverseSteerSpeed = transport->spawnArgs.GetFloat( "simplesteer_reverse_speed", "-4.0" ); minSteerCenteringSpeed = transport->spawnArgs.GetFloat( "simplesteer_centering_speed_min", "2.0" ); maxSteerCenteringSpeed = transport->spawnArgs.GetFloat( "simplesteer_centering_speed_max", "15.0" ); airSteerCenteringSpeed = transport->spawnArgs.GetFloat( "simplesteer_centering_speed_air", "0.5" ); maxSteerCenteringThreshold = transport->spawnArgs.GetFloat( "simplesteer_centering_ramp_threshold", "20.0" ); reverseSteerAngleScale = transport->spawnArgs.GetFloat( "simplesteer_reverse_angle_scale", "-0.5" ); keySteering = false; desiredDirection = 0.0f; } /* ================ sdWheeledVehicleControl::OnPlayerEntered ================ */ void sdWheeledVehicleControl::OnPlayerEntered( idPlayer* player, int position, int oldPosition ) { StopCareening(); idPlayer* driver = owner->GetPositionManager().FindDriver(); if ( driver && driver == player ) { desiredDirection = owner->GetPhysics()->GetAxis().ToAngles().yaw; } } /* ================ sdWheeledVehicleControl::OnTeleport ================ */ void sdWheeledVehicleControl::OnTeleport( void ) { desiredDirection = owner->GetPhysics()->GetAxis().ToAngles().yaw; } /* ================ sdWheeledVehicleControl::SelectGear ================ */ float sdWheeledVehicleControl::SelectGear( idVec3& directions, float absSpeedKPH ) { float min = gearSpeedTable->GetMinValue() * 0.5f; // halving this allows the vehicle to get out of first gear float max = gearSpeedTable->GetMaxValue(); float gearTableFraction; if ( absSpeedKPH < min ) { gearTableFraction = 0.f; } else if ( absSpeedKPH > max ) { gearTableFraction = 1.f; } else { gearTableFraction = ( absSpeedKPH - min ) / ( max - min ); } return gearTableFraction * ( gearForceTable->NumValues() - 2 ); } /* ================ sdWheeledVehicleControl::OnKeyMove ================ */ bool sdWheeledVehicleControl::OnKeyMove( char forward, char right, char up, usercmd_t& cmd ) { const sdVehicleInput& input = owner->GetInput(); idPlayer* player = input.GetPlayer(); if ( player != NULL ) { cmd.forwardmove = forward; cmd.rightmove = right; cmd.upmove = up; if ( right != 0 ) { keySteering = true; } return true; } return false; } /* ================ sdWheeledVehicleControl::OnControllerMove ================ */ void sdWheeledVehicleControl::OnControllerMove( bool doGameCallback, const int numControllers, const int* controllerNumbers, const float** controllerAxis, idVec3& viewAngles, usercmd_t& cmd ) { char oldRightMove = cmd.rightmove; // run the input for each controller for ( int i = 0; i < numControllers; i++ ) { int num = controllerNumbers[ i ]; sdInputModeCar::ControllerMove( doGameCallback, num, controllerAxis[ i ], viewAngles, cmd ); } if ( keySteering ) { // if the keys are being pressed it overrides the controller cmd.rightmove = oldRightMove; } } /* ================ sdWheeledVehicleControl::UpdateControls ================ */ void sdWheeledVehicleControl::UpdateControls() { } /* ================ sdWheeledVehicleControl::SetupInput ================ */ void sdWheeledVehicleControl::SetupInput() { idPlayer* driver = owner->GetPositionManager().FindDriver(); input->Clear(); idVec3 vel = owner->GetPhysics()->GetLinearVelocity(); float speedKPH = InchesToMetres( vel * owner->GetPhysics()->GetAxis()[ 0 ] ) * 3.6f; float absSpeedKPH = fabs( speedKPH ); idVec3 directions = vec3_origin; bool braking = false; bool handbraking = false; float gearForce = 0.0f; float gearSpeed = 0.0f; if ( driver && EngineRunning() ) { input->SetPlayer( driver ); directions.Set( input->GetForward(), input->GetRight(), input->GetUp() ); UpdateCareening( directions ); UpdateDirectionBraking( directions, vel, speedKPH, braking ); UpdateHandbrake( directions, speedKPH, handbraking, braking ); UpdateControls(); float fixedGear = SelectGear( directions, absSpeedKPH ); // limit reverse to first gear only if ( directions.x < 0.0f && speedKPH < 0.0f && fixedGear > 0.5f) { fixedGear = 0.5f; } int intGear = fixedGear; float leftover = fixedGear - intGear; gearForce = gearForceTable->TableLookup( intGear ); gearSpeed = gearSpeedTable->TableLookup( fixedGear ); // adjust the gear force in a kind of torque curve gearForce = gearForce * ( 1.0f + leftover * powerCurveScale ); float steerAngle = UpdateSteering( driver, directions, speedKPH, absSpeedKPH, handbraking ); input->SetSteerAngle( steerAngle ); } else { HandleEmpty( vel, absSpeedKPH, braking ); } UpdateBrakingSound( speedKPH, braking ); SetSpeed( directions, MetresToInches( gearSpeed ) / 3.6f, gearForce, braking, handbraking ); input->SetBraking( braking ); input->SetHandBraking( handbraking ); } /* ================ sdWheeledVehicleControl::UpdateSteering ================ */ float sdWheeledVehicleControl::UpdateSteering( idPlayer* driver, idVec3& directions, float speedKPH, float absSpeedKPH, bool handbraking ) { idAngles currentAngles = owner->GetPhysics()->GetAxis().ToAngles(); if ( driver == NULL ) { idAngles currentAngles = owner->GetPhysics()->GetAxis().ToAngles(); desiredDirection = currentAngles.yaw; return directions.y * steeringAngle; } // auto-steering if ( !handbraking ) { bool reversing = speedKPH < 0.0f; if ( !reversing ) { desiredDirection -= forwardSteerSpeed * directions.y * steeringAngle * MS2SEC( gameLocal.msec ); } else { desiredDirection -= reverseSteerSpeed * directions.y * steeringAngle * MS2SEC( gameLocal.msec ); } float yawDiff = idMath::AngleNormalize180( desiredDirection - currentAngles.yaw ); if ( yawDiff > steeringAngle ) { desiredDirection = currentAngles.yaw + steeringAngle; yawDiff = steeringAngle; } else if ( yawDiff < -steeringAngle ) { desiredDirection = currentAngles.yaw - steeringAngle; yawDiff = -steeringAngle; } else { // drift the desired towards the current float minDriftSpeed = minSteerCenteringSpeed; float driftSpeed = minDriftSpeed; if ( !owner->GetPhysics()->HasGroundContacts() ) { // if in the air don't drift very fast, otherwise you can lose // your heading a bit fast when going over jumps badly driftSpeed = airSteerCenteringSpeed; } else { if ( absSpeedKPH < maxSteerCenteringThreshold ) { float maxDriftSpeed = 0.5f / MS2SEC( gameLocal.msec ); maxDriftSpeed = Min( 0.5f / MS2SEC( gameLocal.msec ), maxSteerCenteringSpeed ); driftSpeed = Lerp( maxDriftSpeed, minDriftSpeed, absSpeedKPH / maxSteerCenteringThreshold ); } } desiredDirection -= yawDiff * driftSpeed * MS2SEC( gameLocal.msec ); } if ( !reversing ) { return -yawDiff; } else { return -yawDiff * reverseSteerAngleScale; } } else { desiredDirection = currentAngles.yaw - directions.y * steeringAngle; return directions.y * steeringAngle; } } /* ================ sdWheeledVehicleControl::StartCareening ================ */ void sdWheeledVehicleControl::StartCareening() { if ( !gameLocal.isClient ) { owner->SetCareening( gameLocal.time ); } } /* ================ sdWheeledVehicleControl::StopCareening ================ */ void sdWheeledVehicleControl::StopCareening() { if ( !gameLocal.isClient ) { owner->SetCareening( 0 ); } } /* ================ sdWheeledVehicleControl::AutoBrake ================ */ bool sdWheeledVehicleControl::AutoBrake() { idVec3 upAxis = owner->GetPhysics()->GetAxis()[ 2 ]; return upAxis.z < 0.95f; } /* ================ sdWheeledVehicleControl::CanEmptyBrake ================ */ bool sdWheeledVehicleControl::CanEmptyBrake( float speedKPH ) { if ( owner->IsCareening() ) { if ( speedKPH < 2.0f ) { StopCareening(); } else { float t = MS2SEC( owner->GetCareeningTime() ); // could expose these parameters, but they're ok here for now. float value = fmod( t * 6.0f, 4.0f ); if ( value != 3.0f ) { return false; } } } return true; } /* ================ sdWheeledVehicleControl::UpdateCareening ================ */ void sdWheeledVehicleControl::UpdateCareening( idVec3& directions ) { if ( directions.x > 0.0f || directions.x < 0.0f ) { StartCareening(); } else { StopCareening(); } } /* ================ sdWheeledVehicleControl::UpdateDirectionBraking ================ */ void sdWheeledVehicleControl::UpdateDirectionBraking( idVec3& directions, idVec3& vel, float speedKPH, bool& braking ) { float absSpeedKPH = fabs( speedKPH ); if ( directions.x && ( absSpeedKPH > 2.0f ) ) { if ( directions.x < 0.0f ) { braking = speedKPH > 0.0f; } else { braking = speedKPH < 0.0f; } } else if ( !directions.x ) { if ( absSpeedKPH < 10.0f ) { braking = true; } else if ( AutoBrake() && vel.z < 0.0f ) { braking = true; } } } /* ================ sdWheeledVehicleControl::UpdateHandbrake ================ */ void sdWheeledVehicleControl::UpdateHandbrake( idVec3& directions, float speedKPH, bool& handbraking, bool& braking ) { idPlayer* driver = owner->GetPositionManager().FindDriver(); if ( driver == NULL ) { return; } bool wantHandbrake = false; if ( !driver->usercmd.buttons.btn.run ) { wantHandbrake = true; } if ( driver->usercmd.buttons.btn.leanLeft ) { wantHandbrake = true; directions.y = -1.0f; directions.z = -1.0f; } else if ( driver->usercmd.buttons.btn.leanRight ) { wantHandbrake = true; directions.y = 1.0f; directions.z = -1.0f; } if ( driver->usercmd.upmove > 0 ) { wantHandbrake = true; } if ( wantHandbrake ) { if ( fabs( speedKPH ) > 5.0f ) { handbraking = true; } else { braking = true; } } } /* ================ sdWheeledVehicleControl::HandleEmpty ================ */ void sdWheeledVehicleControl::HandleEmpty( idVec3& vel, float absSpeedKPH, bool& braking ) { if ( CanEmptyBrake( absSpeedKPH ) ) { if ( absSpeedKPH < 80.0f ) { braking = true; } else if ( AutoBrake() && vel.z < 0.0f ) { braking = true; } } } /* ================ sdWheeledVehicleControl::CanThrust ================ */ bool sdWheeledVehicleControl::CanThrust( idVec3& directions, bool braking, bool handbraking ) { idPlayer* driver = owner->GetPositionManager().FindDriver(); if ( driver ) { if ( directions.x > 0 ) { return driver->usercmd.buttons.btn.sprint && !braking && !handbraking; } } return false; } /* ================ sdWheeledVehicleControl::SetSpeed ================ */ void sdWheeledVehicleControl::SetSpeed( idVec3& directions, float gearSpeed, float gearForce, bool braking, bool handbraking ) { // do the thrusters bool canThrust = CanThrust( directions, braking, handbraking ); bool thrusters = canThrust; if ( thrusters && owner->IsInPlayzone() ) { gearForce *= overDriveFactor; gearSpeed *= overDriveFactor; } UpdateOverdriveSound( thrusters ); // set it up input->SetForce( gearForce * fabs( directions.x ) ); float speed = gearSpeed * directions.x; input->SetLeftSpeed( speed ); input->SetRightSpeed( speed ); } /* ================ sdWheeledVehicleControl::UpdateBrakingSound ================ */ void sdWheeledVehicleControl::UpdateBrakingSound( float speedKPH, bool braking ) { if( braking ) { if( oldSpeedKPH > 50.0f && speedKPH < 50.0f ) { if( nextBrakeSoundTime < gameLocal.time ) { int time; owner->StartSound( "snd_brake", SND_VEHICLE_BRAKE, 0, &time ); nextBrakeSoundTime = gameLocal.time + time; } } } oldSpeedKPH = speedKPH; } /* ================ sdWheeledVehicleControl::GetHudSpeed ================ */ float sdWheeledVehicleControl::GetHudSpeed( void ) const { // wheeled vehicles only read the forward speed (ie from the rotation of the wheels) idVec3 vel = owner->GetPhysics()->GetLinearVelocity(); return idMath::Fabs( vel * owner->GetAxis()[ 0 ] ); } /* ================ sdWheeledVehicleControl::CreateNetworkStructure ================ */ sdEntityStateNetworkData* sdWheeledVehicleControl::CreateNetworkStructure( networkStateMode_t mode ) const { if ( mode == NSM_VISIBLE ) { return new sdWheeledControlNetworkData; } return NULL; } /* ================ sdWheeledVehicleControl::CheckNetworkStateChanges ================ */ bool sdWheeledVehicleControl::CheckNetworkStateChanges( networkStateMode_t mode, const sdEntityStateNetworkData& baseState ) const { if ( mode == NSM_VISIBLE ) { NET_GET_BASE( sdWheeledControlNetworkData ); NET_CHECK_FIELD( desiredDirection, desiredDirection ); if ( input != NULL && baseData.steerVisualAngle != owner->GetSteerVisualAngle() ) { return true; } return false; } return false; } /* ================ sdWheeledVehicleControl::WriteNetworkState ================ */ void sdWheeledVehicleControl::WriteNetworkState( networkStateMode_t mode, const sdEntityStateNetworkData& baseState, sdEntityStateNetworkData& newState, idBitMsg& msg ) const { if ( mode == NSM_VISIBLE ) { NET_GET_STATES( sdWheeledControlNetworkData ); newData.desiredDirection = desiredDirection; newData.steerVisualAngle = owner->GetSteerVisualAngle(); msg.WriteDeltaFloat( baseData.desiredDirection, newData.desiredDirection ); msg.WriteDeltaFloat( baseData.steerVisualAngle, newData.steerVisualAngle ); return; } } /* ================ sdWheeledVehicleControl::ReadNetworkState ================ */ void sdWheeledVehicleControl::ReadNetworkState( networkStateMode_t mode, const sdEntityStateNetworkData& baseState, sdEntityStateNetworkData& newState, const idBitMsg& msg ) const { if ( mode == NSM_VISIBLE ) { NET_GET_STATES( sdWheeledControlNetworkData ); newData.desiredDirection = msg.ReadDeltaFloat( baseData.desiredDirection ); newData.steerVisualAngle = msg.ReadDeltaFloat( baseData.steerVisualAngle ); return; } } /* ================ sdWheeledVehicleControl::ApplyNetworkState ================ */ void sdWheeledVehicleControl::ApplyNetworkState( networkStateMode_t mode, const sdEntityStateNetworkData& newState ) { if ( mode == NSM_VISIBLE ) { NET_GET_NEW( sdWheeledControlNetworkData ); desiredDirection = newData.desiredDirection; owner->SetSteerVisualAngle( newData.steerVisualAngle ); return; } } /* ================ sdWheeledVehicleControl::ResetNetworkState ================ */ void sdWheeledVehicleControl::ResetNetworkState( networkStateMode_t mode, const sdEntityStateNetworkData& newState ) { ApplyNetworkState( mode, newState ); } /* ================ sdWheeledControlNetworkData::MakeDefault ================ */ void sdWheeledControlNetworkData::MakeDefault( void ) { desiredDirection = 0.0f; steerVisualAngle = 0.0f; } /* ================ sdWheeledControlNetworkData::Write ================ */ void sdWheeledControlNetworkData::Write( idFile* file ) const { file->WriteFloat( desiredDirection ); file->WriteFloat( steerVisualAngle ); } /* ================ sdWheeledControlNetworkData::Read ================ */ void sdWheeledControlNetworkData::Read( idFile* file ) { file->ReadFloat( desiredDirection ); file->ReadFloat( steerVisualAngle ); } /* =============================================================================== sdTitanControl =============================================================================== */ /* ================ sdTitanControl::OnKeyMove ================ */ bool sdTitanControl::OnKeyMove( char forward, char right, char up, usercmd_t& cmd ) { return false; } /* ================ sdTitanControl::UpdateCareening ================ */ void sdTitanControl::UpdateCareening( idVec3& directions ) { } /* ================ sdTitanControl::UpdateDirectionBraking ================ */ void sdTitanControl::UpdateDirectionBraking( idVec3& directions, idVec3& vel, float speedKPH, bool& braking ) { float absSpeedKPH = fabs( speedKPH ); if ( directions.x && ( absSpeedKPH > 5.f ) ) { if ( directions.x < 0 ) { braking = speedKPH > 0; } else { braking = speedKPH < 0; } } else if ( directions.x == 0 && directions.y == 0 ) { braking = true; } } /* ================ sdTitanControl::SelectGear ================ */ float sdTitanControl::SelectGear( idVec3& directions, float absSpeedKPH ) { if ( directions.x != 0.0f ) { return sdWheeledVehicleControl::SelectGear( directions, absSpeedKPH ); } else { return 0.0f; } } /* ================ sdTitanControl::UpdateHandbrake ================ */ void sdTitanControl::UpdateHandbrake( idVec3& directions, float speedKPH, bool& handbraking, bool& braking ) { idPlayer* driver = owner->GetPositionManager().FindDriver(); if ( driver != NULL && !driver->usercmd.buttons.btn.run ) { if ( fabs( speedKPH ) > 20.0f ) { handbraking = true; } else { braking = true; } } } /* ================ sdTitanControl::HandleEmpty ================ */ void sdTitanControl::HandleEmpty( idVec3& vel, float absSpeedKPH, bool& braking ) { braking = true; } /* ================ sdTitanControl::UpdateSteering ================ */ float sdTitanControl::UpdateSteering( idPlayer* driver, idVec3& directions, float speedKPH, float absSpeedKPH, bool handbraking ) { // idMat3 desiredMat; // idAngles::YawToMat3( desiredDirection, desiredMat ); // gameRenderWorld->DebugArrow( colorRed, owner->GetPhysics()->GetOrigin(), owner->GetPhysics()->GetOrigin() + desiredMat[ 0 ] * 256.0f, 16 ); idAngles currentAngles = owner->GetPhysics()->GetAxis().ToAngles(); // auto-steering bool reversing = speedKPH < 0.0f && directions.x < 0.0f; if ( !reversing ) { desiredDirection -= forwardSteerSpeed * directions.y * steeringAngle * MS2SEC( gameLocal.msec ); } else { desiredDirection -= reverseSteerSpeed * directions.y * steeringAngle * MS2SEC( gameLocal.msec ); } float yawDiff = idMath::AngleNormalize180( desiredDirection - currentAngles.yaw ); if ( yawDiff > steeringAngle ) { desiredDirection = currentAngles.yaw + steeringAngle; yawDiff = steeringAngle; } else if ( yawDiff < -steeringAngle ) { desiredDirection = currentAngles.yaw - steeringAngle; yawDiff = -steeringAngle; } else { // drift the desired towards the current float driftSpeed = minSteerCenteringSpeed; if ( !owner->GetPhysics()->HasGroundContacts() ) { // if in the air don't drift very fast, otherwise you can lose // your heading a bit fast when going over jumps badly driftSpeed = airSteerCenteringSpeed; } desiredDirection -= yawDiff * driftSpeed * MS2SEC( gameLocal.msec ); } if ( !reversing ) { return -yawDiff; } else { return -yawDiff * reverseSteerAngleScale; } } /* ================ sdTitanControl::SetSpeed ================ */ void sdTitanControl::SetSpeed( idVec3& directions, float gearSpeed, float gearForce, bool braking, bool handbraking ) { // do the thrusters bool canThrust = CanThrust( directions, braking, handbraking ); bool thrusters = canThrust; if ( thrusters && owner->IsInPlayzone() ) { gearSpeed *= overDriveFactor; } UpdateOverdriveSound( thrusters ); idPlayer* driver = owner->GetPositionManager().FindDriver(); gearForce /= overDriveFactor; idVec3 angVelocity = owner->GetPhysics()->GetAngularVelocity(); float yawVelocity = angVelocity * owner->GetPhysics()->GetAxis()[ 2 ]; float angleToSteer = owner->GetInput().GetSteerAngle(); angleToSteer += yawVelocity * 5.0f; float forwardMove = directions.x; float sideMove = angleToSteer / steeringAngle; sideMove = idMath::Fabs( sideMove ) < 0.01f ? 0.0f : sideMove; if ( sideMove > 0.0f ) { sideMove = idMath::Pow( sideMove, 0.25f ); } else { sideMove = -idMath::Pow( -sideMove, 0.25f ); } // yaw speed cancelling float leftSpeed = 0.0f; float rightSpeed = 0.0f; float force = gearForce * ( fabs( forwardMove ) + fabs( sideMove ) ); // if ( driver ) { if( forwardMove != 0.0f ) { gearSpeed = gearSpeed * forwardMove; if( sideMove > 0.0f ) { if ( !thrusters ) { force *= 2.0f; } leftSpeed = gearSpeed * 1.0f; rightSpeed = gearSpeed * ( 1.0f - sideMove * 0.5f ); } else if( directions.y < 0.0f ) { if ( !thrusters ) { force *= 2.0f; } leftSpeed = gearSpeed * ( 1.0f + sideMove * 0.5f ); rightSpeed = gearSpeed * 1.0f; } else { leftSpeed = gearSpeed; rightSpeed = gearSpeed; } } else { if ( !thrusters ) { force *= 4.0f; } leftSpeed = gearSpeed * sideMove * 2.0f; rightSpeed = gearSpeed * sideMove * -2.0f; } // } input->SetLeftSpeed( leftSpeed ); input->SetRightSpeed( rightSpeed ); input->SetForce( force ); } /* =============================================================================== sdTrojanControl =============================================================================== */ /* ================ sdTrojanControl::Init ================ */ void sdTrojanControl::Init( sdTransport* transport ) { sdWheeledVehicleControl::Init( transport ); leftProp.object = NULL; leftProp.angle = 0.f; leftProp.speed = 0.f; leftProp.maxSpeed = 50.f; leftProp.joint = transport->GetAnimator()->GetJointHandle( transport->spawnArgs.GetString( "joint_left_thruster" ) ); rightProp.object = NULL; rightProp.angle = 0.f; rightProp.speed = 0.f; rightProp.maxSpeed = 50.f; rightProp.joint = transport->GetAnimator()->GetJointHandle( transport->spawnArgs.GetString( "joint_right_thruster" ) ); } /* ================ sdTrojanControl::UpdateCareening ================ */ void sdTrojanControl::UpdateCareening( idVec3& directions ) { } /* ================ sdTrojanControl::UpdateControls ================ */ void sdTrojanControl::UpdateControls() { sdWheeledVehicleControl::UpdateControls(); float leftThrust = input->GetForward(); float rightThrust = input->GetForward(); if ( input->GetRight() > 0.0f ) { leftThrust *= 1.75f; rightThrust *= -0.25f; } else if ( input->GetRight() < 0.0f ) { leftThrust *= -0.25f; rightThrust *= 1.75f; } if ( leftProp.object != NULL && rightProp.object != NULL ) { leftProp.object->SetThrust( leftThrust ); rightProp.object->SetThrust( rightThrust ); } } /* ================ sdTrojanControl::UpdatePropeller ================ */ void sdTrojanControl::UpdatePropeller( propeller_t& prop ) { if ( prop.joint == INVALID_JOINT ) { return; } if ( prop.object != NULL ) { float thrust = prop.object->GetThrust(); if ( !prop.object->IsInWater() || thrust == 0.f ) { if ( prop.speed < 0.f ) { prop.speed = prop.speed + 5.f; if ( prop.speed > 0.f ) { prop.speed = 0.f; } } else if ( prop.speed > 0.f ) { prop.speed = prop.speed - 5.f; if ( prop.speed < 0.f ) { prop.speed = 0.f; } } } else { if ( thrust < 0.f ) { prop.speed = prop.speed - 5.f; if ( prop.speed < -prop.maxSpeed ) { prop.speed = -prop.maxSpeed; } } else { prop.speed = prop.speed + 5.f; if ( prop.speed > prop.maxSpeed ) { prop.speed = prop.maxSpeed; } } } if ( prop.speed != 0.f ) { prop.angle += prop.speed; idMat3 temp; idAngles::RollToMat3( prop.angle, temp ); prop.object->GetParent()->GetAnimator()->SetJointAxis( prop.joint, JOINTMOD_LOCAL, temp ); } } } /* ================ sdTrojanControl::Update ================ */ void sdTrojanControl::Update() { // this needs to go here since the drive objects don't exist yet in Init if ( !leftProp.object || !rightProp.object ) { leftProp.object = owner->GetDriveObject( "left_thruster" )->Cast< sdVehicleThruster >(); if ( !leftProp.object ) { gameLocal.Error( "sdTrojanControl::Init - \'left_thruster\' drive object does not exist" ); } rightProp.object = owner->GetDriveObject( "right_thruster" )->Cast< sdVehicleThruster >(); if ( !rightProp.object ) { gameLocal.Error( "sdTrojanControl::Init - \'right_thruster\' drive object does not exist" ); } } sdWheeledVehicleControl::Update(); if ( gameLocal.DoClientSideStuff() && gameLocal.isNewFrame ) { UpdatePropeller( leftProp ); UpdatePropeller( rightProp ); } } /* ================ sdTrojanControl::HandleEmpty ================ */ void sdTrojanControl::HandleEmpty( idVec3& vel, float absSpeedKPH, bool& braking ) { if ( CanEmptyBrake( absSpeedKPH ) ) { if ( absSpeedKPH < 48.0f ) { braking = true; } else if ( AutoBrake() && vel.z < 0.0f ) { braking = true; } if ( leftProp.object != NULL && rightProp.object != NULL ) { leftProp.object->SetThrust( 0.0f ); rightProp.object->SetThrust( 0.0f ); } } } /* =============================================================================== sdPlatypusControl =============================================================================== */ /* ================ sdPlatypusControl::Init ================ */ void sdPlatypusControl::Init( sdTransport* transport ) { sdTrojanControl::Init( transport ); thrustScale = owner->spawnArgs.GetFloat( "thrust_scale" ); steeringSpeedScale = transport->spawnArgs.GetFloat( "steering_speed_scale" ); steeringSpeedMax = transport->spawnArgs.GetFloat( "steering_speed_max" ); steeringSpeedMin = transport->spawnArgs.GetFloat( "steering_speed_min" ); steeringReturnFactor = transport->spawnArgs.GetFloat( "steering_return_factor" ); steeringRampPower = transport->spawnArgs.GetFloat( "steering_ramp_power", "1" ); steeringRampOffset = transport->spawnArgs.GetFloat( "steering_ramp_offset", "0" ); } /* ================ sdPlatypusControl::CalculateSteering ================ */ void sdPlatypusControl::CalculateSteering( idVec3& directions, float absSpeedKPH, float& desiredSteerAngle, float& steeringFactor ) { const sdVehicleInput& input = owner->GetInput(); float keySteerAngle = input.GetSteerAngle(); float steerValue; if ( directions.y != 0.0f && directions.y * keySteerAngle >= 0.0f ) { steerValue = steeringAngle; steeringFactor = fabs( 1.0f - ( absSpeedKPH / steeringSpeedScale ) ); } else { steeringFactor = steeringReturnFactor; steerValue = 0.0f; } steeringFactor = steeringSpeedMax * steeringFactor; if ( steeringFactor < steeringSpeedMin ) { steeringFactor = steeringSpeedMin; } // apply further factors desiredSteerAngle = steerValue * directions.y; ApplySteeringMods( desiredSteerAngle, steeringFactor ); } /* ================ sdPlatypusControl::ApplySteeringMods ================ */ void sdPlatypusControl::ApplySteeringMods( float& desiredSteerAngle, float& steeringFactor ) { float angleDiff = idMath::AngleNormalize180( desiredSteerAngle - input->GetSteerAngle() ); float steerRateRampFactor = angleDiff / ( steeringAngle * 2.0f ); if ( steerRateRampFactor < 0.0f ) { steerRateRampFactor = -steerRateRampFactor; } steerRateRampFactor = idMath::ClampFloat( 0.0f, 1.0f, steerRateRampFactor ); steerRateRampFactor = idMath::Pow( steerRateRampFactor, steeringRampPower ) * ( 1.0f - steeringRampOffset ) + steeringRampOffset; steeringFactor *= steerRateRampFactor; } /* ================ sdPlatypusControl::SetupInput ================ */ void sdPlatypusControl::SetupInput() { idPlayer* driver = owner->GetPositionManager().FindDriver(); input->Clear(); idVec3 vel = owner->GetPhysics()->GetLinearVelocity(); float speedKPH = InchesToMetres( vel * owner->GetPhysics()->GetAxis()[ 0 ] ) * 3.6f; float absSpeedKPH = fabs( speedKPH ); idVec3 directions = vec3_origin; bool thrusters = false; if ( driver && EngineRunning() ) { input->SetPlayer( driver ); directions.Set( input->GetForward(), input->GetRight(), input->GetUp() ); bool canThrust = false; if ( directions.x > 0.0f ) { canThrust = driver->usercmd.buttons.btn.sprint; } thrusters = canThrust; float thrust = directions.x; if ( thrusters ) { thrust *= thrustScale; if ( !owner->IsInPlayzone() ) { thrust *= 0.5f; } } if ( directions.x < 0.0f ) { thrust *= 0.25f; } if ( leftProp.object != NULL && rightProp.object != NULL ) { leftProp.object->SetThrust( thrust ); rightProp.object->SetThrust( thrust ); } if ( driver->usercmd.buttons.btn.leanLeft ) { directions.y = -1.0f; } else if ( driver->usercmd.buttons.btn.leanRight ) { directions.y = 1.0f; } float desiredSteerAngle; float steeringForce; CalculateSteering( directions, absSpeedKPH, desiredSteerAngle, steeringForce ); input->SetSteerSpeed( steeringForce ); input->SetSteerAngle( desiredSteerAngle ); } else { if ( leftProp.object != NULL && rightProp.object != NULL ) { leftProp.object->SetThrust( 0.0f ); rightProp.object->SetThrust( 0.0f ); } } UpdateOverdriveSound( thrusters ); } /* =============================================================================== sdHogControl =============================================================================== */ /* ================ sdHogControl::Init ================ */ void sdHogControl::Init( sdTransport* transport ) { sdWheeledVehicleControl::Init( transport ); ramming = false; ramDamageScale = owner->spawnArgs.GetFloat( "ram_damage_scale" ); hitDamageScale = owner->spawnArgs.GetFloat( "hit_damage_scale" ); } /* ================ sdHogControl::Update ================ */ void sdHogControl::Update() { sdWheeledVehicleControl::Update(); if ( ramming ) { owner->SetDamageDealtScale( ramDamageScale ); } else { owner->SetDamageDealtScale( hitDamageScale ); } if ( ramModel.IsValid() ) { if ( owner->IsHidden() ) { ramModel->Dispose(); ramModel = NULL; } else { renderEntity_t *re = ramModel->GetRenderEntity(); re->suppressSurfaceInViewID = owner->GetRenderEntity()->suppressSurfaceInViewID; re->suppressShadowInViewID = owner->GetRenderEntity()->suppressShadowInViewID; re->shaderParms[ 5 ] = owner->GetPhysics()->GetLinearVelocity().Length(); } // in-cockpit shader idPlayer* driver = owner->GetPositionManager().FindDriver(); if ( driver == gameLocal.GetLocalViewPlayer() ) { sdClientAnimated* cockpit = gameLocal.playerView.GetCockpit(); if ( cockpit != NULL ) { cockpit->GetRenderEntity()->shaderParms[ 5 ] = owner->GetPhysics()->GetLinearVelocity().Length(); } } } } /* ================ sdHogControl::CanThrust ================ */ bool sdHogControl::CanThrust( idVec3& directions, bool braking, bool handbraking ) { idPlayer* driver = owner->GetPositionManager().FindDriver(); bool canThrust = false; if ( driver != NULL ) { canThrust = driver->usercmd.buttons.btn.sprint && !braking && !handbraking && directions.x > 0.0f; } ramming = canThrust; if ( canThrust && !owner->IsHidden() ) { if ( !ramModel.IsValid() ) { const idDeclEntityDef* def = gameLocal.declEntityDefType[ owner->spawnArgs.GetString( "def_ram" ) ]; if ( def != NULL ) { ramModel = new sdClientAnimated(); ramModel->Create( &def->dict, gameLocal.program->GetDefaultType() ); ramModel->Bind( owner ); } } } else { if ( ramModel.IsValid() ) { ramModel->Dispose(); ramModel = NULL; } } return canThrust; } /* ================ sdHogControl::IgnoreCollisionDamage ================ */ bool sdHogControl::IgnoreCollisionDamage( const idVec3& direction ) const { if ( !ramming ) { return false; } float frontNess = direction * owner->GetPhysics()->GetAxis()[ 0 ]; if ( frontNess > 0.7f ) { return true; } return false; } /* =============================================================================== sdHuskyControl =============================================================================== */ /* ================ sdHuskyControl::Init ================ */ void sdHuskyControl::Init( sdTransport* transport ) { sdWheeledVehicleControl::Init( transport ); handleBars = owner->GetAnimator()->GetJointHandle( owner->spawnArgs.GetString( "joint_steer" ) ); } /* ================ sdHuskyControl::Update ================ */ void sdHuskyControl::Update() { sdWheeledVehicleControl::Update(); if ( handleBars != INVALID_JOINT ) { float steerAngle = owner->GetSteerVisualAngle(); idMat3 steerAxis; owner->GetAnimator()->GetJointTransform( handleBars, gameLocal.time, steerAxis ); idAngles steerAngles = steerAxis.ToAngles(); if ( fabs( steerAngles.yaw - steerAngle ) > 0.5f ) { steerAngles.Zero(); steerAngles.yaw = -steerAngle * 1.0f; steerAxis = steerAngles.ToMat3(); owner->GetAnimator()->SetJointAxis( handleBars, JOINTMOD_LOCAL, steerAxis ); } } } /* =============================================================================== sdAirVehicleControl =============================================================================== */ /* ================ sdAirVehicleControl::sdAirVehicleControl ================ */ sdAirVehicleControl::sdAirVehicleControl() { } /* ================ sdAirVehicleControl::Init ================ */ void sdAirVehicleControl::Init( sdTransport* transport ) { sdVehicleControlBase::Init( transport ); idDict& args = owner->spawnArgs; throttling = false; landingGearDown = true; landingThresholdDistance = MetresToInches( args.GetFloat( "landing_threshold_distance" ) ); landingThresholdSpeed = KPHtoUPS( args.GetFloat( "landing_threshold_speed" ) ); deadZoneFraction = false; overDriveFactor = args.GetFloat( "overdrive_factor" ); spiralHealth = 0.0f; collective = 1.0f; collectiveMin = args.GetFloat( "collective_min" ); collectiveMax = args.GetFloat( "collective_max" ); collectiveRate = args.GetFloat( "collective_rate" ); collectiveDefault = 0.0f; lastThrusterEffectsTime = 0; lastDriverTime = 0; isGrounded = true; isLanding = true; landingGearChangeTime = 0; landingGearChangeEndTime = 0; leftJet = NULL; rightJet = NULL; airBrake = NULL; leftThrustEffectJoint = owner->GetAnimator()->GetJointHandle( args.GetString( "left_thrust_effect_joint" ) ); rightThrustEffectJoint = owner->GetAnimator()->GetJointHandle( args.GetString( "right_thrust_effect_joint" ) ); noThrusters = false; height = -1.f; oldCmdAngles.Zero(); careenHeight = args.GetFloat( "careen_height", "512" ); careenSpeed = args.GetFloat( "careen_speed", "150" ); careenYaw = args.GetFloat( "careen_yaw", "0.5" ); careenRoll = args.GetFloat( "careen_roll", "50" ); careenPitch = args.GetFloat( "careen_pitch", "25" ); careenPitchExtreme = args.GetFloat( "careen_pitch_extreme", "35" ); careenLift = args.GetFloat( "careen_lift", "1" ); careenLiftExtreme = args.GetFloat( "careen_lift_extreme", "0" ); careenCollective = args.GetFloat( "careen_collective", "0" ); careenCruiseTime = ( int )( 1000.0f * args.GetFloat( "careen_cruise_time", "0.5" ) ); owner->SetLightsEnabled( 0, false ); } /* ================ sdAirVehicleControl::SetupComponents ================ */ void sdAirVehicleControl::SetupComponents( void ) { leftJet = owner->GetDriveObject( "left_thruster" )->Cast< sdVehicleThruster >(); rightJet = owner->GetDriveObject( "right_thruster" )->Cast< sdVehicleThruster >(); if ( !leftJet || !rightJet ) { noThrusters = true; } airBrake = owner->GetDriveObject( "air_brake" )->Cast< sdVehicleAirBrake >(); mainBounds.Clear(); for ( int i = 0; i < owner->GetPhysics()->GetNumClipModels(); i++ ) { int contents = owner->GetPhysics()->GetContents( i ); if ( contents && contents != MASK_HURTZONE ) { mainBounds.AddBounds( owner->GetPhysics()->GetBounds( i ) ); } } } /* ================ sdAirVehicleControl::Update ================ */ void sdAirVehicleControl::Update() { RunStateMachine(); HandlePhysics(); SetupInput(); } /* ================ sdAirVehicleControl::EngineRunning ================ */ bool sdAirVehicleControl::EngineRunning() { if ( owner->GetPhysics()->InWater() > drownHeight ) { return false; } return ( isLanding || ( gameLocal.time < landingGearChangeEndTime ) ); } /* ================ sdAirVehicleControl::RunStateMachine ================ */ void sdAirVehicleControl::RunStateMachine() { bool emped = owner->IsEMPed(); idPlayer* driver = owner->GetPositionManager().FindDriver(); idVec3 absMins = owner->GetPhysics()->GetAbsBounds()[ 0 ]; idVec3 absMaxs = owner->GetPhysics()->GetAbsBounds()[ 1 ]; idVec3 traceOrg = ( absMins + absMaxs ) * 0.5f; idVec3 traceEnd = traceOrg; traceEnd.z -= 4096.0f; trace_t traceObject; gameLocal.clip.TracePoint( CLIP_DEBUG_PARMS traceObject, traceOrg, traceEnd, MASK_SOLID | CONTENTS_WATER | MASK_OPAQUE, owner ); traceEnd = traceObject.endpos; if ( traceObject.fraction < 1.0f ) { height = traceOrg.z - traceEnd.z; } else { height = -1.0f; } bool contact = IsContacting( absMins, traceObject ); if( landingGearDown ) { isGrounded = contact; deadZoneFraction = ( absMins.z - traceObject.endpos.z ) / landingThresholdDistance; deadZoneFraction = 1.0f - idMath::ClampFloat( 0.0f, 1.0f, deadZoneFraction ); } else { isGrounded = false; deadZoneFraction = 0.0f; } if ( landingGearChangeEndTime < gameLocal.time ) { if ( !isLanding && ( owner->GetPositionManager().IsEmpty() || emped ) ) { isLanding = true; landingGearChangeTime = gameLocal.time; if ( owner->GetHealth() > 0 ) { owner->StartSound( "snd_engine_stop", SND_VEHICLE_DRIVE, 0, NULL ); landingGearChangeEndTime = gameLocal.time + 2000; } else { landingGearChangeEndTime = gameLocal.time + 500; } owner->SetLightsEnabled( 0, false ); } else if ( isLanding && ( driver && !emped ) ) { isLanding = false; landingGearChangeTime = gameLocal.time; owner->StartSound( "snd_engine_start", SND_VEHICLE_DRIVE, 0, NULL ); landingGearChangeEndTime = gameLocal.time + 50; owner->SetLightsEnabled( 0, true ); } } UpdateEffects( absMins, traceObject ); } /* ================ sdAirVehicleControl::HandlePhysics ================ */ void sdAirVehicleControl::HandlePhysics() { owner->UpdateEngine( EngineRunning() ); } /* ================ sdAirVehicleControl::OnKeyMove ================ */ bool sdAirVehicleControl::OnKeyMove( char forward, char right, char up, usercmd_t& cmd ) { return false; } /* ================ sdAirVehicleControl::OnControllerMove ================ */ void sdAirVehicleControl::OnControllerMove( bool doGameCallback, const int numControllers, const int* controllerNumbers, const float** controllerAxis, idVec3& viewAngles, usercmd_t& cmd ) { // run the input for each controller for ( int i = 0; i < numControllers; i++ ) { int num = controllerNumbers[ i ]; sdInputModeHeli::ControllerMove( doGameCallback, num, controllerAxis[ i ], viewAngles, cmd ); } } /* ================ sdAirVehicleControl::SetupInput ================ */ void sdAirVehicleControl::SetupInput() { idPlayer* driver = owner->GetPositionManager().FindDriver(); input->Clear(); idVec3 directions = vec3_origin; idAngles cmdAngles = ang_zero; if ( driver != NULL ) { input->SetPlayer( driver ); directions.Set( input->GetForward(), input->GetRight(), input->GetUp() ); cmdAngles = input->GetCmdAngles(); } idAngles angleDelta = cmdAngles - oldCmdAngles; angleDelta.Normalize180(); oldCmdAngles = cmdAngles; bool simpleControls = driver && !driver->GetUserInfo().advancedFlightControls; float pitchInput = -angleDelta.pitch; float rollInput = -angleDelta.yaw; float yawInput = directions[ 1 ]; if ( driver != NULL ) { if ( driver->GetUserInfo().swapFlightYawAndRoll ) { Swap( rollInput, yawInput ); yawInput *= 2.0f; } yawInput = idMath::ClampFloat( -2.0f, 2.0f, yawInput ); } if ( simpleControls ) { // Newbie mode! // Harvest data float timeDelta = MS2SEC( gameLocal.msec ); idMat3 axis = owner->GetPhysics()->GetAxis(); idAngles angles = axis.ToAngles(); idVec3 origin = owner->GetPhysics()->GetOrigin(); idVec3 linearVelocity = owner->GetPhysics()->GetLinearVelocity(); float linearSpeed = linearVelocity.Length(); idVec3 angVelocity = owner->GetPhysics()->GetAngularVelocity(); idMat3 yawAxis; idAngles::YawToMat3( angles.yaw, yawAxis ); float pitchVelocity = angVelocity * yawAxis[ 1 ]; float rollVelocity = angVelocity * axis[ 0 ]; // Hooray for magic numbers! // TODO - move these magic numbers into the def files. It doesn't seem necessary at the moment // as these values work well for all current flying vehicles, but one day // modders may make some crazy vehicle that makes this need tweaking // calculate if we should autolevel or not bool autoLevellingRoll = false; bool autoLevellingPitch = false; float autoLevelPitchScale = 1.0f; float autoLevelRollScale = 1.0f; if ( idMath::Fabs( rollInput ) < 0.1f && idMath::Fabs( pitchInput ) < 0.1f && idMath::Fabs( yawInput ) < 0.1f ) { if ( simpleControls || deadZoneFraction ) { autoLevellingRoll = true; autoLevellingPitch = true; } } // scale the autolevel using speed float autoLevelScale = ( linearSpeed - 200.0f ) / 1000.0f; autoLevelScale = 1.0f - idMath::ClampFloat( 0.0f, 1.0f, autoLevelScale ); autoLevelScale = autoLevelScale * autoLevelScale; autoLevelPitchScale *= autoLevelScale; if ( idMath::Fabs( yawInput ) < 0.1f ) { autoLevelScale = autoLevelScale * 0.5f + 0.5f; } else { autoLevelScale = autoLevelScale * 0.75f + 0.25f; } autoLevelRollScale *= autoLevelScale; // don't let the player get too out of control! float minPitch = -1000.0f; float maxPitch = 1000.0f; float minRoll = -1000.0f; float maxRoll = 1000.0f; // don't allow them to tilt over too far if ( angles.pitch > 45.0f || angles.pitch < -30.0f ) { float desiredPitch = idMath::ClampFloat( -30.0f, 45.0f, angles.pitch ); // calculate the pitching input needed to get back towards the limit float levellingPitch = ( angles.pitch - desiredPitch ) * timeDelta + pitchVelocity * 2.0f; if ( desiredPitch < 0.0f ) { maxPitch = levellingPitch < 0.0f ? levellingPitch : 0.0f; } else { minPitch = levellingPitch > 0.0f ? levellingPitch : 0.0f; } } if ( idMath::Fabs( angles.roll ) > 30.0f ) { float desiredRoll = idMath::ClampFloat( -30.0f, 30.0f, angles.roll ); // calculate the rolling input needed to get back towards the limit float levellingRoll = ( desiredRoll - angles.roll ) * timeDelta - rollVelocity; if ( desiredRoll > 0.0f ) { maxRoll = levellingRoll < 0.0f ? levellingRoll : 0.0f; } else { minRoll = levellingRoll > 0.0f ? levellingRoll : 0.0f; } } // stop them from giving so much input they start to spin out of control if ( pitchVelocity > 0.8f ) { minPitch = minPitch < 1.0f ? 1.0f : minPitch; } else if ( pitchVelocity < -0.8f ) { maxPitch = maxPitch > -1.0f ? -1.0f : maxPitch; } if ( rollVelocity > 0.8f ) { maxRoll = maxRoll > -1.0f ? -1.0f : maxRoll; } else if ( rollVelocity < -0.8f ) { minRoll = minRoll < 1.0f ? 1.0f : minRoll; } rollInput = idMath::ClampFloat( minRoll, maxRoll, rollInput ); pitchInput = idMath::ClampFloat( minPitch, maxPitch, pitchInput ); // perform the autolevelling if ( autoLevellingRoll ) { float autoLevelRoll = -angles.roll * 0.15f - rollVelocity * 10.0f; rollInput += autoLevelRoll * autoLevelRollScale; } if ( autoLevellingPitch ) { float autoLevelPitch = ( angles.pitch * 0.15f + pitchVelocity * 10.0f ) * 0.15f; rollInput += autoLevelPitch * autoLevelPitchScale; } } input->SetPitch( pitchInput ); input->SetRoll( rollInput ); input->SetYaw( yawInput ); float frameTime = MS2SEC( gameLocal.msec ); if ( directions.x != 0.0f ) { if ( !isGrounded ) { collective = collective + ( directions.x * frameTime * collectiveRate ); } else { collective = directions.x; } collectiveDefault = 0.0f; } else { float diff = collective - collectiveDefault; float rate = frameTime * collectiveRate; if ( fabs( diff ) < rate ) { collective = collectiveDefault; } else { if ( diff > 0.0f ) { collective = collective - rate; } else { collective = collective + rate; } } } if ( collective < collectiveMin ) { collective = collectiveMin; } else if ( collective > collectiveMax ) { collective = collectiveMax; } bool canThrust = false; bool run = false; bool sprint = false; if ( driver ) { run = driver->usercmd.buttons.btn.run; sprint = driver->usercmd.buttons.btn.sprint; canThrust = ( sprint | !run ) && !owner->IsEMPed(); lastDriverTime = gameLocal.time; } else { if ( gameLocal.time - lastDriverTime > SEC2MS( 5 ) || owner->GetPositionManager().IsEmpty() ) { collectiveDefault = collectiveMin; } } UpdateLandingGear( directions ); float thrust = 0.0f; bool thrusters = canThrust && !owner->IsEMPed(); bool careening = owner->IsCareening() && !owner->InDeathThroes(); if ( careening ) { thrusters = true; run = true; } if ( thrusters ) { if ( !run ) { thrust = -overDriveFactor; if( driver == gameLocal.GetLocalPlayer() && driver ) { gameLocal.SetGUIFloat( GUI_GLOBALS_HANDLE, "vehicles.overDriveFraction", 0.0f ); } } else { thrust = overDriveFactor; if( driver == gameLocal.GetLocalPlayer() && driver ) { gameLocal.SetGUIFloat( GUI_GLOBALS_HANDLE, "vehicles.overDriveFraction", 1.0f ); } } if ( !owner->IsInPlayzone() ) { thrust *= 0.5f; } if ( gameLocal.isNewFrame ) { if ( !overDrivePlayingSound ) { owner->StartSound( "snd_overdrive", SND_VEHICLE_OVERDRIVE, 0, NULL ); owner->FadeSound( SND_VEHICLE_OVERDRIVE, 0.0f, 0.01f ); overDrivePlayingSound = true; } } } else { if ( gameLocal.isNewFrame ) { if ( overDrivePlayingSound ) { owner->FadeSound( SND_VEHICLE_OVERDRIVE, -60.f, 1.f ); owner->StartSound( "snd_overdrive_stop", SND_VEHICLE_OVERDRIVE, 0, NULL ); overDrivePlayingSound = false; } } if( driver == gameLocal.GetLocalPlayer() && driver ) { gameLocal.SetGUIFloat( GUI_GLOBALS_HANDLE, "vehicles.overDriveFraction", 0.5f ); } } if ( thrusters && gameLocal.time >= ( lastThrusterEffectsTime + 1000 ) ) { idVec3 white( 1.0f, 1.0f, 1.0f ); bool played = false; if ( run ) { if ( leftThrustEffectJoint != INVALID_JOINT ) { played |= owner->PlayEffect( "fx_thruster_left", white, NULL, leftThrustEffectJoint ) != NULL; } if ( rightThrustEffectJoint != INVALID_JOINT ) { played |= owner->PlayEffect( "fx_thruster_right", white, NULL, rightThrustEffectJoint ) != NULL; } } else { if ( leftThrustEffectJoint != INVALID_JOINT ) { played |= owner->PlayEffect( "fx_thruster_reverse_left", white, NULL, leftThrustEffectJoint ) != NULL; } if ( rightThrustEffectJoint != INVALID_JOINT ) { played |= owner->PlayEffect( "fx_thruster_reverse_right", white, NULL, rightThrustEffectJoint ) != NULL; } } if ( played ) { lastThrusterEffectsTime = gameLocal.time; } } if ( airBrake != NULL ) { if ( thrust < 0.f ) { airBrake->Enable(); thrust = 0.f; } else { airBrake->Disable(); } } if ( careening ) { thrust *= 0.5f; } if ( !noThrusters ) { leftJet->SetThrust( thrust ); rightJet->SetThrust( thrust ); } input->SetCollective( collective ); // hack so that careening vehicles blow up when contacting ground even if it the suspension touching if ( !gameLocal.isClient ) { if ( ( owner->IsCareening() || owner->InDeathThroes() ) && owner->GetPhysics()->HasGroundContacts() ) { sdVehicle_RigidBody* rbOwner = owner->Cast< sdVehicle_RigidBody >(); if ( rbOwner != NULL ) { // kill self const sdDeclDamage* collideDamage = rbOwner->GetCollideDamage(); if ( collideDamage != NULL ) { owner->Damage( owner, owner, idVec3( 0.0f, 0.0f, 1.0f ), collideDamage, 10000.0f, NULL ); } } } } } /* ================ sdAirVehicleControl::IsContacting ================ */ bool sdAirVehicleControl::IsContacting( const idVec3& absMins, const trace_t& traceObject ) { return ( absMins.z - traceObject.endpos.z ) < landingThresholdDistance; } /* ================ sdAirVehicleControl::OnPlayerEntered ================ */ void sdAirVehicleControl::OnPlayerEntered( idPlayer* player, int position, int oldPosition ) { if ( !gameLocal.isClient ) { owner->SetCareening( 0 ); } } /* ================ sdAirVehicleControl::OnPlayerExited ================ */ void sdAirVehicleControl::OnPlayerExited( idPlayer* player, int position ) { if ( !gameLocal.isClient && owner->GetPositionManager().IsEmpty() ) { // check careening conditions const idVec3& velocity = owner->GetPhysics()->GetLinearVelocity(); if ( velocity.LengthSqr() > careenSpeed*careenSpeed ) { owner->SetCareening( gameLocal.time ); } else { idBounds bounds = mainBounds; const idVec3& origin = owner->GetPhysics()->GetOrigin(); const idMat3& axis = owner->GetPhysics()->GetAxis(); bounds.Rotate( axis ); trace_t trace; gameLocal.clip.TraceBounds( CLIP_DEBUG_PARMS trace, origin, origin - idVec3( 0.0f, 0.0f, careenHeight ), bounds, mat3_identity, CONTENTS_SOLID, owner ); if ( trace.fraction >= 1.0f ) { owner->SetCareening( gameLocal.time ); } } } } /* ================ sdAirVehicleControl::OnPostDamage ================ */ void sdAirVehicleControl::OnPostDamage( idEntity* attacker, int oldHealth, int newHealth ) { if ( spiralHealth > 0 && newHealth <= spiralHealth && oldHealth > spiralHealth ) { owner->GetPhysics()->Activate(); owner->SetDeathThroes( true ); owner->GetPositionManager().EjectAllPlayers( EF_KILL_PLAYERS ); } else if ( newHealth > spiralHealth ) { owner->SetDeathThroes( false ); } } /* ================ sdAirVehicleControl::GetCareeningCollideScale ================ */ float sdAirVehicleControl::GetCareeningCollideScale() const { return 1000.0f; } /* ================ sdAirVehicleControl::GetCareeningRollAmount ================ */ float sdAirVehicleControl::GetCareeningRollAmount() const { float angle = 0.0f; int time = owner->GetCareeningTime(); if ( time > careenCruiseTime ) { float bankSpeed = owner->GetPhysics()->GetAngularVelocity() * owner->GetPhysics()->GetAxis()[ 0 ]; if ( bankSpeed <= 0.0f ) { angle = -careenRoll; } else { angle = careenRoll; } } return angle; } /* ================ sdAirVehicleControl::GetCareeningPitchAmount ================ */ float sdAirVehicleControl::GetCareeningPitchAmount() const { int time = owner->GetCareeningTime(); if ( time < careenCruiseTime ) { float pitchSpeed = owner->GetPhysics()->GetAngularVelocity() * owner->GetPhysics()->GetAxis()[ 1 ]; if ( pitchSpeed <= 0.0f ) { return careenPitch; } else { return -careenPitch; } } else { return -careenPitchExtreme; } } /* ================ sdAirVehicleControl::GetCareeningYawAmount ================ */ float sdAirVehicleControl::GetCareeningYawAmount() const { // calculate the direction this should be spinning in float angle = 0.0f; int time = owner->GetCareeningTime(); if ( time > careenCruiseTime ) { float yawSpeed = owner->GetPhysics()->GetAngularVelocity() * owner->GetPhysics()->GetAxis()[ 2 ]; if ( yawSpeed <= 0.0f ) { angle = -careenYaw; } else { angle = careenYaw; } } return angle; } /* ================ sdAirVehicleControl::GetCareeningLiftScale ================ */ float sdAirVehicleControl::GetCareeningLiftScale() const { int time = owner->GetCareeningTime(); if ( time < careenCruiseTime * 2.0f ) { return careenLift; } return careenLiftExtreme; } /* ================ sdAirVehicleControl::GetCareeningCollectiveAmount ================ */ float sdAirVehicleControl::GetCareeningCollectiveAmount() const { return careenCollective; } /* =============================================================================== sdHornetControl =============================================================================== */ /* ================ sdHornetControl::Init ================ */ void sdHornetControl::Init( sdTransport* transport ) { sdAirVehicleControl::Init( transport ); owner->StartSound( "snd_throttle", SND_VEHICLE_IDLE, 0, NULL ); owner->FadeSound( SND_VEHICLE_IDLE, -60.0f, 0.0f ); groundEffects = false; groundEffectsThreshhold = owner->spawnArgs.GetFloat( "groundeffects_threshhold" ); lastGroundEffectsTime = 0; landingAnimEndTime = 0; } /* ================ sdHornetControl::OnPlayerEntered ================ */ void sdHornetControl::OnPlayerEntered( idPlayer* player, int position, int oldPosition ) { sdAirVehicleControl::OnPlayerEntered( player, position, oldPosition ); if ( position == 0 ) { const char *sparks = owner->spawnArgs.GetString( "joints_up_sparks" ); if ( sparks && *sparks ) { idStrList placement; idSplitStringIntoList( placement, sparks, ";" ); for (int i=0; iGetAnimator()->GetJointHandle( placement[i] ); if ( jh != INVALID_JOINT ) { idVec3 white(1.f,1.f,1.f); owner->PlayEffect( "fx_up_sparks", white, NULL, jh ); } } } } } /* ================ sdHornetControl::OnPlayerEntered ================ */ void sdHornetControl::OnEMPStateChanged( void ) { if ( !owner->IsEMPed() ) { owner->StartSound( "snd_engine_start", SND_VEHICLE_DRIVE, 0, NULL ); owner->StartSound( "snd_throttle", SND_VEHICLE_IDLE, 0, NULL ); owner->FadeSound( SND_VEHICLE_IDLE, -60.0f, 0.0f ); } else { owner->StopSound( SND_VEHICLE_IDLE ); owner->StartSound( "snd_engine_stop", SND_VEHICLE_DRIVE, 0, NULL ); } } /* ================ sdHornetControl::UpdateEffects ================ */ void sdHornetControl::UpdateEffects( const idVec3& absMins, const trace_t& traceObject ) { idPlayer* driver = owner->GetPositionManager().FindDriver(); if( driver && landingGearChangeEndTime < gameLocal.time ) { const idVec3& physics = owner->GetPhysics()->GetLinearVelocity(); owner->FadeSound( SND_VEHICLE_IDLE, ( collective * 30.0f ) - 10.0f, collectiveRate * 0.1f ); owner->SetChannelPitchShift( SND_VEHICLE_IDLE, physics.Length() * 0.0005f + 1.0f ); } else { owner->FadeSound( SND_VEHICLE_IDLE, -60.0f, 0.5f ); owner->SetChannelPitchShift( SND_VEHICLE_IDLE, 1.0f ); } groundEffects = ( absMins.z - traceObject.endpos.z ) < groundEffectsThreshhold; idVec3 white( 1.0f, 1.0f, 1.0f ); bool isEmpty = owner->GetPositionManager().IsEmpty(); /* if ( !downdraftPlaying && !isEmpty ) { if ( mainJoint != INVALID_JOINT && leftJetJoint != INVALID_JOINT && rightJetJoint != INVALID_JOINT ) { owner->PlayEffect( "fx_downdraft", white, NULL, mainJoint, true ); owner->PlayEffect( "fx_thruster_base_right", white, NULL, leftJetJoint, true ); owner->PlayEffect( "fx_thruster_base_left", white, NULL, rightJetJoint, true ); } downdraftPlaying = true; } else if ( downdraftPlaying && isEmpty ) { owner->StopAllEffects(); downdraftPlaying = false; }*/ if ( gameLocal.time >= ( lastGroundEffectsTime + 100 ) && !isEmpty && groundEffects && traceObject.fraction < 1.0f && gameLocal.time > landingGearChangeEndTime ) { const char* surfaceTypeName = NULL; if ( traceObject.c.surfaceType ) { surfaceTypeName = traceObject.c.surfaceType->GetName(); } owner->PlayEffect( "fx_groundeffect", white, surfaceTypeName, traceObject.endpos, traceObject.c.normal.ToMat3(), 0 ); lastGroundEffectsTime = gameLocal.time; } } /* ================ sdHornetControl::IsContacting ================ */ bool sdHornetControl::IsContacting( const idVec3& absMins, const trace_t& traceObject ) { if ( owner->IsAtRest() && owner->GetPhysics()->HasGroundContacts() ) { return true; } return ( absMins.z - traceObject.endpos.z ) < landingThresholdDistance; } /* ================ sdHornetControl::UpdateLandingGear ================ */ void sdHornetControl::UpdateLandingGear( const idVec3& directions ) { if ( landingAnimEndTime > gameLocal.time ) { return; } float speed = owner->GetPhysics()->GetLinearVelocity() * owner->GetPhysics()->GetAxis()[ 0 ]; float absSpeed = fabs( speed ); int anim = 0; if ( height < landingThresholdDistance && absSpeed < landingThresholdSpeed && directions.z <= 0.0f ) { if( !landingGearDown ) { landingGearDown = true; anim = owner->GetAnimator()->GetAnim( "gear_down" ); sdScriptHelper h1; owner->GetScriptObject()->CallNonBlockingScriptEvent( owner->GetScriptObject()->GetFunction( "OnLandingGearDown" ), h1 ); } } else { if ( landingGearDown ) { landingGearDown = false; anim = owner->GetAnimator()->GetAnim( "gear_up" ); sdScriptHelper h1; owner->GetScriptObject()->CallNonBlockingScriptEvent( owner->GetScriptObject()->GetFunction( "OnLandingGearUp" ), h1 ); } } if ( anim ) { owner->GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, 0 ); landingAnimEndTime = gameLocal.time + owner->GetAnimator()->AnimLength( anim ); } } /* =============================================================================== sdHovercopterControl =============================================================================== */ /* ================ sdHovercopterControl::Init ================ */ void sdHovercopterControl::Init( sdTransport* transport ) { sdAirVehicleControl::Init( transport ); downdraftPlaying = false; groundEffects = false; groundEffectsThreshhold = owner->spawnArgs.GetFloat( "groundeffects_threshhold" ); lastGroundEffectsTime = 0; mainJoint = owner->GetAnimator()->GetJointHandle( "main" ); leftJetJoint = owner->GetAnimator()->GetJointHandle( "left_thruster" ); rightJetJoint = owner->GetAnimator()->GetJointHandle( "right_thruster" ); } /* ================ sdHovercopterControl::UpdateEffects ================ */ void sdHovercopterControl::UpdateEffects( const idVec3& absMins, const trace_t& traceObject ) { groundEffects = ( absMins.z - traceObject.endpos.z ) < groundEffectsThreshhold; idVec3 white( 1.0f, 1.0f, 1.0f ); bool isEmpty = owner->GetPositionManager().IsEmpty(); if ( !downdraftPlaying && !isEmpty ) { if ( mainJoint != INVALID_JOINT && leftJetJoint != INVALID_JOINT && rightJetJoint != INVALID_JOINT ) { owner->PlayEffect( "fx_downdraft", white, NULL, mainJoint, true ); owner->PlayEffect( "fx_thruster_base_right", white, NULL, leftJetJoint, true ); owner->PlayEffect( "fx_thruster_base_left", white, NULL, rightJetJoint, true ); } downdraftPlaying = true; } else if ( downdraftPlaying && isEmpty ) { owner->StopEffect( "fx_downdraft" ); owner->StopEffect( "fx_thruster_base_right" ); owner->StopEffect( "fx_thruster_base_left" ); downdraftPlaying = false; } if ( gameLocal.time >= ( lastGroundEffectsTime + 100 ) && !isEmpty && groundEffects && traceObject.fraction < 1.0f && gameLocal.time > landingGearChangeEndTime ) { const char* surfaceTypeName = NULL; if ( traceObject.c.surfaceType ) { surfaceTypeName = traceObject.c.surfaceType->GetName(); } owner->PlayEffect( "fx_groundeffect", white, surfaceTypeName, traceObject.endpos, traceObject.c.normal.ToMat3(), false, vec3_origin, false ); lastGroundEffectsTime = gameLocal.time; } } /* =============================================================================== sdAnansiControl =============================================================================== */ /* ================ sdAnansiControl::Init ================ */ void sdAnansiControl::Init( sdTransport* transport ) { sdHovercopterControl::Init( transport ); landingAnimEndTime = 0; } /* ================ sdAnansiControl::UpdateLandingGear ================ */ void sdAnansiControl::UpdateLandingGear( const idVec3& directions ) { if ( landingAnimEndTime > gameLocal.time ) { return; } float speed = owner->GetPhysics()->GetLinearVelocity() * owner->GetPhysics()->GetAxis()[ 0 ]; float absSpeed = fabs( speed ); int anim = 0; if ( height < landingThresholdDistance && absSpeed < landingThresholdSpeed && directions.z <= 0.0f ) { if( !landingGearDown ) { landingGearDown = true; anim = owner->GetAnimator()->GetAnim( "gear_down" ); sdScriptHelper h1; owner->GetScriptObject()->CallNonBlockingScriptEvent( owner->GetScriptObject()->GetFunction( "OnLandingGearDown" ), h1 ); } } else { if ( landingGearDown ) { landingGearDown = false; anim = owner->GetAnimator()->GetAnim( "gear_up" ); sdScriptHelper h1; owner->GetScriptObject()->CallNonBlockingScriptEvent( owner->GetScriptObject()->GetFunction( "OnLandingGearUp" ), h1 ); } } if ( anim ) { owner->GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, 0 ); landingAnimEndTime = gameLocal.time + owner->GetAnimator()->AnimLength( anim ); } } /* =============================================================================== sdWalkerNetworkData =============================================================================== */ /* ================ sdWalkerNetworkData::MakeDefault ================ */ void sdWalkerNetworkData::MakeDefault( void ) { idealYaw = 0.f; currentYaw = 0.f; turnScale = 0.f; crouching = false; } /* ================ sdWalkerNetworkData::Write ================ */ void sdWalkerNetworkData::Write( idFile* file ) const { file->WriteFloat( idealYaw ); file->WriteFloat( currentYaw ); file->WriteFloat( turnScale ); file->WriteBool( crouching ); } /* ================ sdWalkerNetworkData::Read ================ */ void sdWalkerNetworkData::Read( idFile* file ) { file->ReadFloat( idealYaw ); file->ReadFloat( currentYaw ); file->ReadFloat( turnScale ); file->ReadBool( crouching ); } /* =============================================================================== sdWalkerControl =============================================================================== */ #define IsTurningOnSpot() ( ( newState == CS_TURN_LEFT ) || ( newState == CS_TURN_RIGHT ) ) #define MovingBackwards() ( ( newState == CS_WALK_BACK_LEFT_LEG ) || ( newState == CS_WALK_BACK_RIGHT_LEG ) || ( newState == CS_WALK_BACK_LEFT_LEG_START ) ) /* ================ sdWalkerControl::Init ================ */ void sdWalkerControl::Init( sdTransport* transport ) { sdVehicleControlBase::Init( transport ); walker = owner->Cast< sdWalker >(); if ( !walker ) { gameLocal.Error( "sdWalkerControl::Init Walker control can only be used on sdWalker based entities" ); } owner->SetLightsEnabled( 0, false ); owner->SetLightsEnabled( 1, true ); walker->SetCompressionScale( 0.9f, 0.1f ); idAnimator* animator = transport->GetAnimator(); for ( int i = 0; i < CS_NUM_STATES; i++ ) { stateAnims[ i ] = animator->GetAnim( owner->spawnArgs.GetString( va( "anim_state_%i", i ) ) ); } dynamicTurnRate = 0.f; currentYaw = 0; idealYaw = owner->GetRenderEntity()->axis.ToAngles().yaw; turnScale = 0.f; turnRate = owner->spawnArgs.GetFloat( "turn_rate" ); currentState = CS_SPAWN; newState = CS_SPAWN; newStateTime = 0; leftFootJoint = walker->GetAnimator()->GetJointHandle( walker->spawnArgs.GetString( "joint_foot_left" ) ); rightFootJoint = walker->GetAnimator()->GetJointHandle( walker->spawnArgs.GetString( "joint_foot_right" ) ); leftFootOnGround = false; rightFootOnGround = false; lastLeftFootEffectTime = 0; lastRightFootEffectTime = 0; lastLeftFootGroundOrg.Zero(); lastRightFootGroundOrg.Zero(); lastLeftFootOrg.Zero(); lastRightFootOrg.Zero(); fallStartTime = 0; flags.powered = false; flags.turnOnSpot = owner->spawnArgs.GetBool( "turn_on_spot" ); flags.playStopAnim = owner->spawnArgs.GetBool( "play_stop_anim", "1" ); flags.manualCrouch = false; flags.startOnLeftLeg = owner->spawnArgs.GetBool( "start_on_left", "1" ); walkingGroundPoundForce = owner->spawnArgs.GetFloat( "ground_pound_walk_force", "10000000" ); walkingGroundPoundDamageScale = owner->spawnArgs.GetFloat( "ground_pound_walk_damage_scale", "0.25" ); walkingGroundPoundRange = owner->spawnArgs.GetFloat( "ground_pound_walk_range", "384" ); } /* ================ sdWalkerControl::Update ================ */ void sdWalkerControl::Update() { RunStateMachine(); owner->UpdateEngine( !flags.powered ); Move(); if ( gameLocal.isNewFrame ) { // no point spawning sliding foot effects in reprediction UpdateSliding(); } } /* ================ sdWalkerControl::TurnToward ================ */ void sdWalkerControl::TurnToward( float yaw ) { idealYaw = idMath::AngleNormalize180( yaw ); } /* ================ sdWalkerControl::Turn ================ */ void sdWalkerControl::Turn( void ) { if ( idMath::Fabs( idealYaw - currentYaw ) < 0.01f ) { return; } float rate; if ( IsTurningOnSpot() ) { rate = dynamicTurnRate; } else { rate = turnRate; } float ang = idMath::AngleNormalize180( idealYaw - currentYaw ); float maxTurn = rate * gameLocal.msec; float newAngles; if ( ang < -maxTurn ) { newAngles = currentYaw - maxTurn; } else if( ang > maxTurn ) { newAngles = currentYaw + maxTurn; } else { newAngles = idealYaw; } SetYaw( newAngles ); } /* ================ sdWalkerControl::OnYawChanged ================ */ void sdWalkerControl::OnYawChanged( float newYaw ) { currentYaw = newYaw; idealYaw = newYaw; } /* ================ sdWalkerControl::SetPowered ================ */ void sdWalkerControl::SetPowered( bool value ) { if ( flags.powered == value ) { return; } flags.powered = value; if ( flags.powered ) { owner->StartSound( "snd_powerup", SND_ANY, 0, NULL ); owner->StartSound( "snd_engine_start_interior", SND_VEHICLE_INTERIOR_IDLE, 0, NULL ); owner->StartSound( "snd_engine_start", SND_VEHICLE_IDLE, 0, NULL ); owner->SetLightsEnabled( 0, true ); const char *sparks = owner->spawnArgs.GetString( "joints_up_sparks" ); if ( sparks && *sparks ) { idStrList placement; idSplitStringIntoList( placement, sparks, ";" ); for (int i=0; iGetAnimator()->GetJointHandle( placement[i] ); if ( jh != INVALID_JOINT ) { idVec3 white(1.f,1.f,1.f); owner->PlayEffect( "fx_up_sparks", white, NULL, jh ); } } } walker->SetCompressionScale( 0.3f, 1.f ); } else { owner->StartSound( "snd_shutdown", SND_ANY, 0, NULL ); owner->StartSound( "snd_engine_stop_interior", SND_VEHICLE_INTERIOR_IDLE, 0, NULL ); owner->StartSound( "snd_engine_stop", SND_VEHICLE_IDLE, 0, NULL ); owner->SetLightsEnabled( 0, false ); walker->SetCompressionScale( 0.9f, 1.f ); } } /* ================ sdWalkerControl::OnNewStateBegun ================ */ void sdWalkerControl::OnNewStateBegun( controlState_t state ) { switch ( state ) { case CS_SHUTDOWN: SetPowered( false ); break; case CS_POSED: SetPowered( true ); break; } } /* ================ sdWalkerControl::OnNewStateCompleted ================ */ void sdWalkerControl::OnNewStateCompleted( controlState_t state ) { switch ( state ) { case CS_WALK_LEFT_LEG: case CS_WALK_LEFT_LEG_START: case CS_WALK_BACK_LEFT_LEG: case CS_WALK_BACK_LEFT_LEG_START: case CS_WALK_RIGHT_LEG: case CS_WALK_RIGHT_LEG_START: case CS_WALK_BACK_RIGHT_LEG: case CS_WALK_BACK_RIGHT_LEG_START: { walker->GroundPound( walkingGroundPoundForce, walkingGroundPoundDamageScale, walkingGroundPoundRange ); break; } } } /* ================ sdWalkerControl::OnOldStateFinished ================ */ void sdWalkerControl::OnOldStateFinished( controlState_t state ) { } /* ================ sdWalkerControl::SetupNextState ================ */ bool sdWalkerControl::SetupNextState( controlState_t state, int time, int blendTime ) { if ( !stateAnims[ state ] ) { return false; } idAnimator* animator = owner->GetAnimator(); const idAnim* anim = animator->GetAnim( stateAnims[ state ] ); int length = anim->Length(); if ( newState == state && newStateTime == time + length ) { return true; } newState = state; newStateTime = time + length; newStateStartTime = time; animator->PlayAnim( ANIMCHANNEL_LEGS, stateAnims[ state ], time, blendTime ); // Gordon: Need to test with and without this, as i'd rather not do this if we don't "have" to /* if ( gameLocal.isServer ) { sdEntityBroadcastEvent msg( owner, sdTransport::EVENT_CONTROLMESSAGE ); msg.WriteBits( state, idMath::BitsForInteger( CS_NUM_STATES ) ); msg.WriteLong( blendTime ); msg.Send( false, false ); }*/ float turnScale = 0.f; switch ( state ) { case CS_TURN_RIGHT: turnScale = -1.f; break; case CS_TURN_LEFT: turnScale = 1.f; break; } if ( turnScale != 0.f ) { TurnToward( currentYaw + ( turnScale * turnRate ) ); dynamicTurnRate = turnRate / length; } else { TurnToward( currentYaw ); } OnNewStateBegun( state ); return true; } /* ================ sdWalkerControl::OnKeyMove ================ */ bool sdWalkerControl::OnKeyMove( char forward, char right, char up, usercmd_t& cmd ) { return false; } /* ================ sdWalkerControl::OnControllerMove ================ */ void sdWalkerControl::OnControllerMove( bool doGameCallback, const int numControllers, const int* controllerNumbers, const float** controllerAxis, idVec3& viewAngles, usercmd_t& cmd ) { // run the input for each controller for ( int i = 0; i < numControllers; i++ ) { int num = controllerNumbers[ i ]; sdInputModePlayer::ControllerMove( doGameCallback, num, controllerAxis[ i ], viewAngles, cmd ); } } idCVar g_walkerTraceDistance( "g_walkerTraceDistance", "128", CVAR_GAME | CVAR_FLOAT | CVAR_NETWORKSYNC | CVAR_RANKLOCKED, "distance to check for space for the walker to move" ); /* ================ sdWalkerControl::CheckWalk ================ */ bool sdWalkerControl::CheckWalk( float direction ) { const idMat3& walkerAxes = walker->GetAxis(); float distance = g_walkerTraceDistance.GetFloat(); if ( distance <= 0.f ) { return true; } idVec3 forward = walkerAxes[ 0 ] * direction * distance; idVec3 up( 0.f, 0.f, walker->GetMonsterPhysics().GetMaxStepHeight() ); idClipModel* cm = walker->GetPhysics()->GetClipModel(); const idVec3& origin = walker->GetPhysics()->GetOrigin(); trace_t trace; gameLocal.clip.TranslationWorld( CLIP_DEBUG_PARMS trace, origin, origin + forward, cm, mat3_identity, walker->GetPhysics()->GetClipMask() ); if ( trace.fraction == 1.f ) { return true; } gameLocal.clip.TranslationWorld( CLIP_DEBUG_PARMS trace, origin + up, origin + up + forward, cm, mat3_identity, walker->GetPhysics()->GetClipMask() ); if ( trace.fraction == 1.f ) { return true; } return false; } /* ================ sdWalkerControl::CheckStateExit ================ */ bool sdWalkerControl::CheckStateExit( void ) { if ( newState == CS_FALL ) { if ( walker->GetMonsterPhysics().OnGround() ) { SetupNextState( CS_LAND, gameLocal.time, 50 ); return true; } return false; } if ( gameLocal.time - newStateStartTime < 100 ) { // we've only jsut started return false; } if ( newStateTime - gameLocal.time < 100 ) { // we're close to the end return false; } switch ( newState ) { case CS_WALK_RIGHT_LEG_START: case CS_WALK_LEFT_LEG: case CS_WALK_LEFT_LEG_START: case CS_WALK_RIGHT_LEG: { if ( input->GetForward() <= 0.f ) { SetupNextState( CS_STAND, gameLocal.time, 250 ); return true; } break; } case CS_WALK_BACK_LEFT_LEG_START: case CS_WALK_BACK_LEFT_LEG: case CS_WALK_BACK_RIGHT_LEG_START: case CS_WALK_BACK_RIGHT_LEG: { if ( input->GetForward() >= 0.f ) { SetupNextState( CS_STAND, gameLocal.time, 250 ); return true; } break; } case CS_TURN_LEFT: { if ( input->GetRight() >= 0.f ) { SetupNextState( CS_STAND, gameLocal.time, 250 ); return true; } break; } case CS_TURN_RIGHT: { if ( input->GetRight() <= 0.f ) { SetupNextState( CS_STAND, gameLocal.time, 250 ); return true; } break; } } return false; } /* ================ sdWalkerControl::RunStateMachine ================ */ void sdWalkerControl::RunStateMachine( void ) { idPlayer* driver = owner->GetPositionManager().FindDriver(); input->Clear(); input->SetPlayer( driver ); if ( newStateTime != 0 ) { if ( gameLocal.time < newStateTime ) { CheckStateExit(); return; } OnOldStateFinished( currentState ); currentState = newState; newState = CS_SPAWN; newStateTime = 0; OnNewStateCompleted( currentState ); } bool alive = owner->GetHealth() > 0; bool wantsToCrouch = !driver || flags.manualCrouch || !alive || owner->IsEMPed(); switch ( currentState ) { case CS_SPAWN: SetupNextState( CS_SHUTDOWN, gameLocal.time, 50 ); break; case CS_SHUTDOWN: if ( !wantsToCrouch ) { SetupNextState( CS_POSED, gameLocal.time, 50 ); } break; case CS_STAMP: case CS_STOMP_END: case CS_TURN_LEFT: case CS_TURN_RIGHT: case CS_WALK_RIGHT_LEG_STOP: case CS_WALK_LEFT_LEG_STOP: case CS_POSED: case CS_STAND: case CS_WALK_BACK_LEFT_LEG_STOP: case CS_WALK_BACK_RIGHT_LEG_STOP: case CS_LAND: if ( walker->GetMonsterPhysics().OnGround() ) { fallStartTime = 0; if ( wantsToCrouch ) { SetupNextState( CS_SHUTDOWN, gameLocal.time, 50 ); } else { float forward = input->GetForward(); if ( forward > 0.f && CheckWalk( 1.f ) ) { if ( flags.startOnLeftLeg ) { SetupNextState( CS_WALK_LEFT_LEG_START, gameLocal.time, 50 ); } else { SetupNextState( CS_WALK_RIGHT_LEG_START, gameLocal.time, 50 ); } } else if ( forward < 0.f && CheckWalk( -1.f ) ) { if ( flags.startOnLeftLeg ) { SetupNextState( CS_WALK_BACK_LEFT_LEG_START, gameLocal.time, 50 ); } else { SetupNextState( CS_WALK_BACK_RIGHT_LEG_START, gameLocal.time, 50 ); } } else if ( flags.turnOnSpot ) { float right = input->GetRight(); if ( right > 0.f ) { SetupNextState( CS_TURN_RIGHT, gameLocal.time, 50 ); } else if ( right < 0.f ) { SetupNextState( CS_TURN_LEFT, gameLocal.time, 50 ); } } if ( input->GetUp() > 0.f ) { SetupNextState( CS_STAMP, gameLocal.time, 50 ); } } } else { if ( fallStartTime == 0 ) { fallStartTime = gameLocal.time; } if ( ( gameLocal.time - fallStartTime ) > SEC2MS( 0.33f ) ) { fallStartTime = 0; SetupNextState( CS_FALL, gameLocal.time, 50 ); } } break; case CS_FALL: if ( walker->GetMonsterPhysics().OnGround() ) { SetupNextState( CS_LAND, gameLocal.time, 50 ); } break; case CS_WALK_RIGHT_LEG_START: case CS_WALK_LEFT_LEG: { float forward = input->GetForward(); if ( wantsToCrouch || forward <= 0.f || !CheckWalk( 1.f ) ) { SetupNextState( CS_WALK_RIGHT_LEG_STOP, gameLocal.time, 50 ); } else { SetupNextState( CS_WALK_RIGHT_LEG, gameLocal.time, 50 ); } break; } case CS_WALK_LEFT_LEG_START: case CS_WALK_RIGHT_LEG: { float forward = input->GetForward(); if ( wantsToCrouch || forward <= 0.f || !CheckWalk( 1.f ) ) { SetupNextState( CS_WALK_LEFT_LEG_STOP, gameLocal.time, 50 ); } else { SetupNextState( CS_WALK_LEFT_LEG, gameLocal.time, 50 ); } break; } case CS_WALK_BACK_LEFT_LEG_START: case CS_WALK_BACK_LEFT_LEG: { float forward = input->GetForward(); if ( wantsToCrouch || forward >= 0.f || !CheckWalk( -1.f ) ) { SetupNextState( CS_WALK_BACK_RIGHT_LEG_STOP, gameLocal.time, 50 ); } else { SetupNextState( CS_WALK_BACK_RIGHT_LEG, gameLocal.time, 50 ); } break; } case CS_WALK_BACK_RIGHT_LEG_START: case CS_WALK_BACK_RIGHT_LEG: { float forward = input->GetForward(); if ( wantsToCrouch || forward >= 0.f || !CheckWalk( -1.f ) ) { SetupNextState( CS_WALK_BACK_LEFT_LEG_STOP, gameLocal.time, 50 ); } else { SetupNextState( CS_WALK_BACK_LEFT_LEG, gameLocal.time, 50 ); } break; } case CS_STOMP_START: { SetupNextState( CS_STOMP_END, gameLocal.time, 50 ); break; } } } /* ================ sdWalkerControl::Move ================ */ void sdWalkerControl::Move( void ) { if ( flags.powered && !IsTurningOnSpot() ) { float lerpScale; if ( walker->GetMonsterPhysics().OnGround() ) { lerpScale = 0.05f; } else { lerpScale = 0.1f; } float turning = 0.f; if ( input->GetRight() ) { turning = MS2SEC( gameLocal.msec ) * turnRate; if ( input->GetRight() > 0.f ) { turning *= -1; } if ( MovingBackwards() ) { turning *= -1; } } turnScale = turnScale + ( ( turning - turnScale ) * lerpScale ); TurnToward( currentYaw + turnScale ); } else { turnScale = 0.f; } if ( input->GetUp() > 0.f ) { flags.manualCrouch = false; } else if( input->GetUp() < 0.f ) { flags.manualCrouch = true; } idVec3 delta; walker->GetMoveDelta( delta ); walker->SetDelta( delta ); Turn(); } /* ================ sdWalkerControl::SetYaw ================ */ void sdWalkerControl::SetYaw( float yaw ) { float oldIdealYaw = idealYaw; idMat3 temp; idAngles::YawToMat3( yaw, temp ); owner->SetAxis( temp ); idealYaw = oldIdealYaw; } /* ================ sdWalkerControl::UpdateSlidingFoot ================ */ void sdWalkerControl::UpdateSlidingFoot( jointHandle_t joint, bool& footOnGround, idVec3& lastFootOrg, idVec3& lastFootGroundOrg, int& lastFootEffectTime ) { bool wasOnGround = footOnGround; footOnGround = false; idVec3 footOrg; walker->GetWorldOrigin( joint, footOrg ); // check if the foot is on the ground idVec3 traceStart = footOrg; idVec3 traceEnd = footOrg; traceStart.z += 20.0f; traceEnd.z -= 10.0f; trace_t traceObject; gameLocal.clip.TracePoint( CLIP_DEBUG_PARMS traceObject, traceStart, traceEnd, MASK_SOLID | CONTENTS_WATER | MASK_OPAQUE, owner ); traceEnd = traceObject.endpos; if ( traceObject.fraction < 1.0f ) { float traceDist = traceObject.fraction * 30.0f - 20.0f; idBounds checkBounds( traceEnd ); checkBounds.ExpandSelf( 8.0f ); const idClipModel* waterModel; int found = gameLocal.clip.FindWater( CLIP_DEBUG_PARMS checkBounds, &waterModel, 1 ); int cont = 0; if ( found ) { cont = gameLocal.clip.ContentsModel( CLIP_DEBUG_PARMS traceEnd, NULL, mat3_identity, CONTENTS_WATER, waterModel, waterModel->GetOrigin(), waterModel->GetAxis() ); } if ( !cont ) { idVec3 footGroundOrg = traceEnd; bool playEffect = false; // on the ground and not in water const idVec3& groundNormal = walker->GetMonsterPhysics().GetGroundNormal(); if ( wasOnGround ) { // find the movement on the ground plane idVec3 groundDelta = footGroundOrg - lastFootGroundOrg; groundDelta -= ( groundDelta * groundNormal ) * groundNormal; if ( groundDelta.LengthSqr() > 25.0f ) { playEffect = true; } } else { // find the movement towards the ground plane float towardsGround = ( lastFootOrg - footOrg ) * groundNormal; if ( towardsGround > 4.0f && towardsGround < 100.0f ) { playEffect = true; } } if ( playEffect && lastFootEffectTime < gameLocal.time - 150 ) { const char* surfaceTypeName = NULL; if ( traceObject.c.surfaceType ) { surfaceTypeName = traceObject.c.surfaceType->GetName(); } idVec3 colorWhite(1.f,1.f,1.f); idVec3 xaxis(-1.f, 0.f, 0.f); walker->PlayEffect( "fx_ground_walk", colorWhite, surfaceTypeName, traceEnd, xaxis.ToMat3() ); lastFootEffectTime = gameLocal.time; } footOnGround = true; lastFootGroundOrg = footGroundOrg; } } lastFootOrg = footOrg; } /* ================ sdWalkerControl::UpdateSliding ================ */ void sdWalkerControl::UpdateSliding() { UpdateSlidingFoot( leftFootJoint, leftFootOnGround, lastLeftFootOrg, lastLeftFootGroundOrg, lastLeftFootEffectTime ); UpdateSlidingFoot( rightFootJoint, rightFootOnGround, lastRightFootOrg, lastRightFootGroundOrg, lastRightFootEffectTime ); } /* ================ sdWalkerControl::ApplyNetworkState ================ */ void sdWalkerControl::ApplyNetworkState( networkStateMode_t mode, const sdEntityStateNetworkData& newState ) { if ( mode == NSM_VISIBLE ) { NET_GET_NEW( sdWalkerNetworkData ); idPlayer* localViewer = gameLocal.GetLocalViewPlayer(); if ( localViewer == NULL || walker->GetPositionManager().FindDriver() != localViewer ) { idealYaw = newData.idealYaw; } SetYaw( newData.currentYaw ); turnScale = newData.turnScale; flags.manualCrouch = newData.crouching; } } /* ================ sdWalkerControl::ReadNetworkState ================ */ void sdWalkerControl::ReadNetworkState( networkStateMode_t mode, const sdEntityStateNetworkData& baseState, sdEntityStateNetworkData& newState, const idBitMsg& msg ) const { if ( mode == NSM_VISIBLE ) { NET_GET_STATES( sdWalkerNetworkData ); newData.idealYaw = msg.ReadDeltaFloat( baseData.idealYaw ); newData.currentYaw = msg.ReadDeltaFloat( baseData.currentYaw ); newData.turnScale = msg.ReadDeltaFloat( baseData.turnScale ); newData.crouching = msg.ReadBool(); } } /* ================ sdWalkerControl::WriteNetworkState ================ */ void sdWalkerControl::WriteNetworkState( networkStateMode_t mode, const sdEntityStateNetworkData& baseState, sdEntityStateNetworkData& newState, idBitMsg& msg ) const { if ( mode == NSM_VISIBLE ) { NET_GET_STATES( sdWalkerNetworkData ); newData.idealYaw = idealYaw; newData.currentYaw = currentYaw; newData.turnScale = turnScale; newData.crouching = flags.manualCrouch; msg.WriteDeltaFloat( baseData.idealYaw, newData.idealYaw ); msg.WriteDeltaFloat( baseData.currentYaw, newData.currentYaw ); msg.WriteDeltaFloat( baseData.turnScale, newData.turnScale ); msg.WriteBool( newData.crouching ); } } /* ================ sdWalkerControl::CheckNetworkStateChanges ================ */ bool sdWalkerControl::CheckNetworkStateChanges( networkStateMode_t mode, const sdEntityStateNetworkData& baseState ) const { if ( mode == NSM_VISIBLE ) { NET_GET_BASE( sdWalkerNetworkData ); if ( baseData.idealYaw != idealYaw ) { return true; } if ( baseData.currentYaw != currentYaw ) { return true; } if ( baseData.turnScale != turnScale ) { return true; } if ( baseData.crouching != flags.manualCrouch ) { return true; } } return false; } /* ================ sdWalkerControl::CreateNetworkStructure ================ */ sdEntityStateNetworkData* sdWalkerControl::CreateNetworkStructure( networkStateMode_t mode ) const { if ( mode == NSM_VISIBLE ) { return new sdWalkerNetworkData; } return NULL; } /* ================ sdWalkerControl::OnNetworkEvent ================ */ void sdWalkerControl::OnNetworkEvent( int time, const idBitMsg& msg ) { controlState_t state = ( controlState_t )msg.ReadBits( idMath::BitsForInteger( CS_NUM_STATES ) ); SetupNextState( state, time, msg.ReadLong() ); }