etqw-sdk/source/game/vehicles/VehicleControl.cpp

3557 lines
98 KiB
C++

// 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; i<placement.Num(); i++) {
jointHandle_t jh = owner->GetAnimator()->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; i<placement.Num(); i++) {
jointHandle_t jh = owner->GetAnimator()->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() );
}