text: //========= Copyright Valve Corporation, All rights reserved. ============//
  //
  // Purpose:
  //
  // $NoKeywords: $
  //=============================================================================//
  
  #include "cbase.h"
  #include "vehicle_base.h"
  #include "engine/IEngineSound.h"
  #include "in_buttons.h"
  #include "soundenvelope.h"
  #include "soundent.h"
  #include "physics_saverestore.h"
  #include "vphysics/constraints.h"
  #include "vcollide_parse.h"
  #include "ndebugoverlay.h"
  #include "npc_vehicledriver.h"
  #include "vehicle_crane.h"
  #include "hl2_player.h"
  #include "rumble_shared.h"
  
  // memdbgon must be the last include file in a .cpp file!!!
  #include "tier0/memdbgon.h"
  
  #define VEHICLE_HITBOX_DRIVER       1
  
  extern ConVar g_debug_vehicledriver;
  
  // Crane spring constants
  #define CRANE_SPRING_CONSTANT_HANGING           2e5f
  #define CRANE_SPRING_CONSTANT_INITIAL_RAISING   (CRANE_SPRING_CONSTANT_HANGING * 0.5)
  #define CRANE_SPRING_CONSTANT_LOWERING          30.0f
  #define CRANE_SPRING_DAMPING                    2e5f
  #define CRANE_SPRING_RELATIVE_DAMPING           2
  
  // Crane bones that have physics followers
  static const char *pCraneFollowerBoneNames[] =
  {
      "base",
      "arm",
      "platform",
  };
  
  // Crane tip
  LINK_ENTITY_TO_CLASS( crane_tip, CCraneTip );
  
  BEGIN_DATADESC( CCraneTip )
  
      DEFINE_PHYSPTR( m_pSpring ),
  
  END_DATADESC()
  
  // Crane
  LINK_ENTITY_TO_CLASS( prop_vehicle_crane, CPropCrane );
  
  BEGIN_DATADESC( CPropCrane )
  
      // Inputs
      DEFINE_INPUTFUNC( FIELD_VOID, "Lock",   InputLock ),
      DEFINE_INPUTFUNC( FIELD_VOID, "Unlock", InputUnlock ),
      DEFINE_INPUTFUNC( FIELD_VOID, "ForcePlayerIn",  InputForcePlayerIn ),
  
      // Keys
      DEFINE_EMBEDDED( m_ServerVehicle ),
      DEFINE_EMBEDDED( m_BoneFollowerManager ),
  
      DEFINE_FIELD( m_hPlayer, FIELD_EHANDLE ),
      DEFINE_FIELD( m_bMagnetOn, FIELD_BOOLEAN ),
      DEFINE_FIELD( m_hNPCDriver, FIELD_EHANDLE ),
      DEFINE_FIELD( m_nNPCButtons, FIELD_INTEGER ),
      DEFINE_FIELD( m_bLocked, FIELD_BOOLEAN ),
      DEFINE_FIELD( m_bEnterAnimOn, FIELD_BOOLEAN ),
      DEFINE_FIELD( m_bExitAnimOn, FIELD_BOOLEAN ),
      DEFINE_FIELD( m_vecEyeExitEndpoint, FIELD_POSITION_VECTOR ),
      DEFINE_OUTPUT( m_playerOn, "PlayerOn" ),
      DEFINE_OUTPUT( m_playerOff, "PlayerOff" ),
      DEFINE_FIELD( m_iTurning, FIELD_INTEGER ),
      DEFINE_FIELD( m_bStartSoundAtCrossover, FIELD_BOOLEAN ),
      DEFINE_FIELD( m_flTurn, FIELD_FLOAT ),
      DEFINE_FIELD( m_bExtending, FIELD_BOOLEAN ),
      DEFINE_FIELD( m_flExtension, FIELD_FLOAT ),
      DEFINE_FIELD( m_flExtensionRate, FIELD_FLOAT ),
      DEFINE_FIELD( m_bDropping, FIELD_BOOLEAN ),
      //DEFINE_FIELD( m_flNextDangerSoundTime, FIELD_TIME ),
      //DEFINE_FIELD( m_flNextCreakSound, FIELD_TIME ),
      DEFINE_FIELD( m_flNextDropAllowedTime, FIELD_TIME ),
      DEFINE_FIELD( m_flSlowRaiseTime, FIELD_TIME ),
      DEFINE_FIELD( m_flMaxExtensionSpeed, FIELD_FLOAT ),
      DEFINE_FIELD( m_flMaxTurnSpeed, FIELD_FLOAT ),
      DEFINE_FIELD( m_flExtensionAccel, FIELD_FLOAT ),
      DEFINE_FIELD( m_flExtensionDecel, FIELD_FLOAT ),
      DEFINE_FIELD( m_flTurnAccel, FIELD_FLOAT ),
      DEFINE_FIELD( m_flTurnDecel, FIELD_FLOAT ),
      DEFINE_KEYFIELD( m_iszMagnetName, FIELD_STRING, "magnetname" ),
      DEFINE_FIELD( m_hCraneMagnet, FIELD_EHANDLE ),
      DEFINE_FIELD( m_hCraneTip, FIELD_EHANDLE ),
      DEFINE_FIELD( m_hRope, FIELD_EHANDLE ),
      DEFINE_PHYSPTR( m_pConstraintGroup ),
      DEFINE_KEYFIELD( m_vehicleScript, FIELD_STRING, "vehiclescript" ),
  
  END_DATADESC()
  
  IMPLEMENT_SERVERCLASS_ST(CPropCrane, DT_PropCrane)
      SendPropEHandle(SENDINFO(m_hPlayer)),
      SendPropBool(SENDINFO(m_bMagnetOn)),
      SendPropBool(SENDINFO(m_bEnterAnimOn)),
      SendPropBool(SENDINFO(m_bExitAnimOn)),
      SendPropVector(SENDINFO(m_vecEyeExitEndpoint), -1, SPROP_COORD),
  END_SEND_TABLE();
  
  
  //------------------------------------------------
  // Precache
  //------------------------------------------------
  void CPropCrane::Precache( void )
  {
      BaseClass::Precache();
      m_ServerVehicle.Initialize( STRING(m_vehicleScript) );
  }
  
  
  //------------------------------------------------
  // Spawn
  //------------------------------------------------
  void CPropCrane::Spawn( void )
  {
      Precache();
      SetModel( STRING( GetModelName() ) );
      SetCollisionGroup( COLLISION_GROUP_VEHICLE );
  
      BaseClass::Spawn();
  
      SetSolid( SOLID_BBOX );
      AddSolidFlags( FSOLID_NOT_SOLID );
      SetMoveType( MOVETYPE_NOCLIP );
  
      m_takedamage = DAMAGE_EVENTS_ONLY;
      m_flTurn = 0;
      m_flExtension = 0;
      m_flNextDangerSoundTime = 0;
      m_flNextCreakSound = 0;
      m_flNextDropAllowedTime = 0;
      m_flSlowRaiseTime = 0;
      m_bDropping = false;
      m_bMagnetOn = false;
  
      InitCraneSpeeds();
  
      SetPoseParameter( "armextensionpose", m_flExtension );
  
      CreateVPhysics();
      SetNextThink( gpGlobals->curtime );
  }
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  void CPropCrane::Activate( void )
  {
      BaseClass::Activate();
  
      // If we load a game, we don't need to set this all up again.
      if ( m_hCraneMagnet )
          return;
  
      // Find our magnet
      if ( m_iszMagnetName == NULL_STRING )
      {
          Warning( "prop_vehicle_crane %s has no magnet entity specified!\n", STRING(GetEntityName()) );
          UTIL_Remove( this );
          return;
      }
  
      m_hCraneMagnet = dynamic_cast<CPhysMagnet *>(gEntList.FindEntityByName( NULL, STRING(m_iszMagnetName) ));
      if ( !m_hCraneMagnet )
      {
          Warning( "prop_vehicle_crane %s failed to find magnet %s.\n", STRING(GetEntityName()), STRING(m_iszMagnetName) );
          UTIL_Remove( this );
          return;
      }
  
      // We want the magnet to cast a long shadow
      m_hCraneMagnet->SetShadowCastDistance( 2048 );
  
      // Create our constraint group
      constraint_groupparams_t group;
      group.Defaults();
      m_pConstraintGroup = physenv->CreateConstraintGroup( group );
      m_hCraneMagnet->SetConstraintGroup( m_pConstraintGroup );
  
      // Create our crane tip
      Vector vecOrigin;
      QAngle vecAngles;
      GetCraneTipPosition( &vecOrigin, &vecAngles );
      m_hCraneTip = CCraneTip::Create( m_hCraneMagnet, m_pConstraintGroup, vecOrigin, vecAngles );
      if ( !m_hCraneTip )
      {
          UTIL_Remove( this );
          return;
      }
      m_pConstraintGroup->Activate();
  
      // Make a rope to connect 'em
      int iIndex = m_hCraneMagnet->LookupAttachment("magnetcable_a");
      m_hRope = CRopeKeyframe::Create( this, m_hCraneMagnet, 1, iIndex );
      if ( m_hRope )
      {
          m_hRope->m_Width = 3;
          m_hRope->m_nSegments = ROPE_MAX_SEGMENTS / 2;
          m_hRope->EnableWind( false );
          m_hRope->SetupHangDistance( 0 );
          m_hRope->m_RopeLength = (m_hCraneMagnet->GetAbsOrigin() - m_hCraneTip->GetAbsOrigin()).Length() * 1.1;
      }
  
      // Start with the magnet off
      TurnMagnetOff();
  }
  
  //-----------------------------------------------------------------------------
  // Purpose:
  // Output : Returns true on success, false on failure.
  //-----------------------------------------------------------------------------
  bool CPropCrane::CreateVPhysics( void )
  {
      BaseClass::CreateVPhysics();
      m_BoneFollowerManager.InitBoneFollowers( this, ARRAYSIZE(pCraneFollowerBoneNames), pCraneFollowerBoneNames );
      return true;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  void CPropCrane::UpdateOnRemove( void )
  {
      m_BoneFollowerManager.DestroyBoneFollowers();
      BaseClass::UpdateOnRemove();
  }
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  void CPropCrane::InitCraneSpeeds( void )
  {
      m_flMaxExtensionSpeed = CRANE_EXTENSION_RATE_MAX * 2;
      m_flMaxTurnSpeed = CRANE_TURN_RATE_MAX * 2;
      m_flExtensionAccel = CRANE_EXTENSION_ACCEL * 2;
      m_flExtensionDecel = CRANE_EXTENSION_DECEL * 2;
      m_flTurnAccel = CRANE_TURN_ACCEL * 2;
      m_flTurnDecel = CRANE_DECEL * 2;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  void CPropCrane::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
  {
      if ( ptr->hitbox == VEHICLE_HITBOX_DRIVER )
      {
          if ( m_hPlayer != NULL )
          {
              m_hPlayer->TakeDamage( info );
          }
      }
  }
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  int CPropCrane::OnTakeDamage( const CTakeDamageInfo &inputInfo )
  {
      //Do scaled up physics damage to the car
      CTakeDamageInfo info = inputInfo;
      info.ScaleDamage( 25 );
  
      // reset the damage
      info.SetDamage( inputInfo.GetDamage() );
  
      //Check to do damage to driver
      if ( m_hPlayer != NULL )
      {
          //Take no damage from physics damages
          if ( info.GetDamageType() & DMG_CRUSH )
              return 0;
  
          //Take the damage
          m_hPlayer->TakeDamage( info );
      }
  
      return 0;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  Vector CPropCrane::BodyTarget( const Vector &posSrc, bool bNoisy )
  {
      Vector  shotPos;
      matrix3x4_t matrix;
  
      int eyeAttachmentIndex = LookupAttachment("vehicle_driver_eyes");
      GetAttachment( eyeAttachmentIndex, matrix );
      MatrixGetColumn( matrix, 3, shotPos );
  
      if ( bNoisy )
      {
          shotPos[0] += random->RandomFloat( -8.0f, 8.0f );
          shotPos[1] += random->RandomFloat( -8.0f, 8.0f );
          shotPos[2] += random->RandomFloat( -8.0f, 8.0f );
      }
  
      return shotPos;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  void CPropCrane::Think(void)
  {
      SetNextThink( gpGlobals->curtime + 0.1 );
  
      if ( GetDriver() )
      {
          BaseClass::Think();
  
          if ( m_hNPCDriver )
          {
              GetServerVehicle()->NPC_DriveVehicle();
          }
  
          // play enter animation
          StudioFrameAdvance();
  
          // If the enter or exit animation has finished, tell the server vehicle
          if ( IsSequenceFinished() && (m_bExitAnimOn || m_bEnterAnimOn) )
          {
              if ( m_bEnterAnimOn )
              {
                  // Finished entering, display the hint for using the crane
                  UTIL_HudHintText( m_hPlayer, "#Valve_Hint_CraneKeys" );
              }
  
              GetServerVehicle()->HandleEntryExitFinish( m_bExitAnimOn, true );
          }
      }
      else
      {
          // Run the crane's movement
          RunCraneMovement( 0.1 );
      }
  
      // Update follower bones
      m_BoneFollowerManager.UpdateBoneFollowers(this);
  }
  
  //-----------------------------------------------------------------------------
  // Purpose:
  // Input  : *player -
  //-----------------------------------------------------------------------------
  void CPropCrane::ItemPostFrame( CBasePlayer *player )
  {
  }
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  void CPropCrane::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
  {
      CBasePlayer *pPlayer = ToBasePlayer( pActivator );
      if ( !pPlayer )
          return;
  
      ResetUseKey( pPlayer );
  
      GetServerVehicle()->HandlePassengerEntry( pPlayer, (value>0) );
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Return true of the player's allowed to enter / exit the vehicle
  //-----------------------------------------------------------------------------
  bool CPropCrane::CanEnterVehicle( CBaseEntity *pEntity )
  {
      // Prevent entering if the vehicle's being driven by an NPC
      if ( GetDriver() && GetDriver() != pEntity )
          return false;
  
      // Prevent entering if the vehicle's locked
      return ( !m_bLocked );
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Return true of the player's allowed to enter / exit the vehicle
  //-----------------------------------------------------------------------------
  bool CPropCrane::CanExitVehicle( CBaseEntity *pEntity )
  {
      // Prevent exiting if the vehicle's locked, or rotating
      // Adrian: Check also if I'm currently jumping in or out.
      return ( !m_bLocked && (GetLocalAngularVelocity() == vec3_angle) && m_bExitAnimOn == false && m_bEnterAnimOn == false );
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Override base class to add display
  //-----------------------------------------------------------------------------
  void CPropCrane::DrawDebugGeometryOverlays(void)
  {
      // Draw if BBOX is on
      if ( m_debugOverlays & OVERLAY_BBOX_BIT )
      {
          Vector vecPoint = m_hCraneMagnet->GetAbsOrigin();
          int iIndex = m_hCraneMagnet->LookupAttachment("magnetcable_a");
          if ( iIndex >= 0 )
          {
              m_hCraneMagnet->GetAttachment( iIndex, vecPoint );
          }
  
          NDebugOverlay::Line( m_hCraneTip->GetAbsOrigin(), vecPoint, 255,255,255, true, 0.1 );
      }
  
      BaseClass::DrawDebugGeometryOverlays();
  }
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  void CPropCrane::EnterVehicle( CBaseCombatCharacter *pPassenger )
  {
      if ( pPassenger == NULL )
          return;
  
      CBasePlayer *pPlayer = ToBasePlayer( pPassenger );
      if ( pPlayer != NULL )
      {
          // Remove any player who may be in the vehicle at the moment
          if ( m_hPlayer )
          {
              ExitVehicle( VEHICLE_ROLE_DRIVER );
          }
  
          m_hPlayer = pPlayer;
          m_playerOn.FireOutput( pPlayer, this, 0 );
  
          m_hPlayer->RumbleEffect( RUMBLE_FLAT_BOTH, 0, RUMBLE_FLAG_LOOP );
          m_hPlayer->RumbleEffect( RUMBLE_FLAT_BOTH, 10, RUMBLE_FLAG_UPDATE_SCALE );
  
          m_ServerVehicle.SoundStart();
      }
      else
      {
          // NPCs not yet supported - jdw
          Assert( 0 );
      }
  }
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  void CPropCrane::ExitVehicle( int nRole )
  {
      CBasePlayer *pPlayer = m_hPlayer;
      if ( !pPlayer )
          return;
  
      m_hPlayer = NULL;
      ResetUseKey( pPlayer );
      m_playerOff.FireOutput( pPlayer, this, 0 );
      m_bEnterAnimOn = false;
  
      m_ServerVehicle.SoundShutdown( 1.0 );
  }
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  void CPropCrane::ResetUseKey( CBasePlayer *pPlayer )
  {
      pPlayer->m_afButtonPressed &= ~IN_USE;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Pass player movement into the crane's driving system
  //-----------------------------------------------------------------------------
  void CPropCrane::SetupMove( CBasePlayer *player, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move )
  {
      // If the player's entering/exiting the vehicle, prevent movement
      if ( !m_bEnterAnimOn && !m_bExitAnimOn )
      {
          int buttons = ucmd->buttons;
          if ( !(buttons & (IN_MOVELEFT|IN_MOVERIGHT)) )
          {
              if ( ucmd->sidemove < 0 )
              {
                  buttons |= IN_MOVELEFT;
              }
              else if ( ucmd->sidemove > 0 )
              {
                  buttons |= IN_MOVERIGHT;
              }
          }
          DriveCrane( buttons, player->m_afButtonPressed );
      }
  
      // Run the crane's movement
      RunCraneMovement( gpGlobals->frametime );
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Crane rotates around with +left and +right, and extends/retracts
  //          the cable with +forward and +back.
  //-----------------------------------------------------------------------------
  void CPropCrane::DriveCrane( int iDriverButtons, int iButtonsPressed, float flNPCSteering )
  {
      bool bWasExtending = m_bExtending;
  
      // Handle rotation of the crane
      if ( iDriverButtons & IN_MOVELEFT )
      {
          // NPCs may cheat and set the steering
          if ( flNPCSteering )
          {
              m_flTurn = flNPCSteering;
          }
          else
          {
              // Try adding some randomness to make it feel shaky?
              float flTurnAdd = m_flTurnAccel;
              // If we're turning back on ourselves, use decel speed
              if ( m_flTurn < 0 )
              {
                  flTurnAdd = MAX( flTurnAdd, m_flTurnDecel );
              }
  
              m_flTurn = UTIL_Approach( m_flMaxTurnSpeed, m_flTurn, flTurnAdd * gpGlobals->frametime );
          }
          m_iTurning = TURNING_LEFT;
      }
      else if ( iDriverButtons & IN_MOVERIGHT )
      {
          // NPCs may cheat and set the steering
          if ( flNPCSteering )
          {
              m_flTurn = flNPCSteering;
          }
          else
          {
              // Try adding some randomness to make it feel shaky?
              float flTurnAdd = m_flTurnAccel;
              // If we're turning back on ourselves, increase the rate
              if ( m_flTurn > 0 )
              {
                  flTurnAdd = MAX( flTurnAdd, m_flTurnDecel );
              }
              m_flTurn = UTIL_Approach( -m_flMaxTurnSpeed, m_flTurn, flTurnAdd * gpGlobals->frametime );
          }
          m_iTurning = TURNING_RIGHT;
      }
      else
      {
          m_flTurn = UTIL_Approach( 0, m_flTurn, m_flTurnDecel * gpGlobals->frametime );
          m_iTurning = TURNING_NOT;
      }
  
      if ( m_hPlayer )
      {
          float maxTurn = GetMaxTurnRate();
          static float maxRumble = 0.35f;
          static float minRumble = 0.1f;
          float rumbleRange = maxRumble - minRumble;
          float rumble;
  
          float factor = fabs(m_flTurn) / maxTurn;
          factor = MIN( factor, 1.0f );
          rumble = minRumble + (rumbleRange * factor);
  
          m_hPlayer->RumbleEffect( RUMBLE_FLAT_BOTH, (int)(rumble * 100), RUMBLE_FLAG_UPDATE_SCALE );
      }
  
      SetLocalAngularVelocity( QAngle(0,m_flTurn * 10,0) );
  
      // Handle extension / retraction of the arm
      if ( iDriverButtons & IN_FORWARD )
      {
          m_flExtensionRate = UTIL_Approach( m_flMaxExtensionSpeed, m_flExtensionRate, m_flExtensionAccel * gpGlobals->frametime );
          m_bExtending = true;
      }
      else if ( iDriverButtons & IN_BACK )
      {
          m_flExtensionRate = UTIL_Approach( -m_flMaxExtensionSpeed, m_flExtensionRate, m_flExtensionAccel * gpGlobals->frametime );
          m_bExtending = true;
      }
      else
      {
          m_flExtensionRate = UTIL_Approach( 0, m_flExtensionRate, m_flExtensionDecel * gpGlobals->frametime );
          m_bExtending = false;
      }
  
      //Msg("Turn: %f\nExtensionRate: %f\n", m_flTurn, m_flExtensionRate );
  
      //If we're holding down an attack button, update our state
      if ( iButtonsPressed & (IN_ATTACK | IN_ATTACK2) )
      {
          // If we have something on the magnet, turn the magnet off
          if ( m_hCraneMagnet->GetTotalMassAttachedObjects() )
          {
              TurnMagnetOff();
          }
          else if ( !m_bDropping && m_flNextDropAllowedTime < gpGlobals->curtime )
          {
              TurnMagnetOn();
  
              // Drop the magnet till it hits something
              m_bDropping = true;
              m_hCraneMagnet->ResetHasHitSomething();
              m_hCraneTip->m_pSpring->SetSpringConstant( CRANE_SPRING_CONSTANT_LOWERING );
  
              m_ServerVehicle.PlaySound( VS_MISC1 );
          }
      }
  
      float flSpeedPercentage = clamp( fabs(m_flTurn) / m_flMaxTurnSpeed, 0, 1 );
      vbs_sound_update_t params;
      params.Defaults();
      params.bThrottleDown = (m_iTurning != TURNING_NOT);
      params.flCurrentSpeedFraction = flSpeedPercentage;
      params.flWorldSpaceSpeed = 0;
  
      m_ServerVehicle.SoundUpdate( params );
  
      // Play sounds for arm extension / retraction
      if ( m_bExtending && !bWasExtending )
      {
          m_ServerVehicle.StopSound( VS_ENGINE2_STOP );
          m_ServerVehicle.PlaySound( VS_ENGINE2_START );
      }
      else if ( !m_bExtending && bWasExtending )
      {
          m_ServerVehicle.StopSound( VS_ENGINE2_START );
          m_ServerVehicle.PlaySound( VS_ENGINE2_STOP );
      }
  }
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  void CPropCrane::RecalculateCraneTip( void )
  {
      Vector vecOrigin;
      QAngle vecAngles;
      GetCraneTipPosition( &vecOrigin, &vecAngles );
      m_hCraneTip->SetAbsOrigin( vecOrigin );
  
      // NOTE: We need to do this because we're not using Physics...
      if ( m_hCraneTip->VPhysicsGetObject() )
      {
          m_hCraneTip->VPhysicsGetObject()->UpdateShadow( vecOrigin, vec3_angle, true, TICK_INTERVAL * 2.0f );
      }
  }
  
  //-----------------------------------------------------------------------------
  // Purpose:
  // Input  : *pPlayer -
  //          *pMoveData -
  //-----------------------------------------------------------------------------
  void CPropCrane::RunCraneMovement( float flTime )
  {
      if ( m_flExtensionRate )
      {
          // Extend / Retract the crane
          m_flExtension = clamp( m_flExtension + (m_flExtensionRate * 10 * flTime), 0, 2 );
          SetPoseParameter( "armextensionpose", m_flExtension );
          StudioFrameAdvance();
      }
  
      // Drop the magnet until it hits the ground
      if ( m_bDropping )
      {
          // Drop until the magnet hits something
          if ( m_hCraneMagnet->HasHitSomething() )
          {
              // We hit the ground, stop dropping
              m_hCraneTip->m_pSpring->SetSpringConstant( CRANE_SPRING_CONSTANT_INITIAL_RAISING );
              m_bDropping = false;
              m_flNextDropAllowedTime = gpGlobals->curtime + 3.0;
              m_flSlowRaiseTime = gpGlobals->curtime;
  
              m_ServerVehicle.PlaySound( VS_MISC2 );
          }
      }
      else if ( (m_flSlowRaiseTime + CRANE_SLOWRAISE_TIME) > gpGlobals->curtime )
      {
          float flDelta = (gpGlobals->curtime - m_flSlowRaiseTime);
  
          flDelta = clamp( flDelta, 0, CRANE_SLOWRAISE_TIME );
          float flCurrentSpringConstant = RemapVal( flDelta, 0, CRANE_SLOWRAISE_TIME, CRANE_SPRING_CONSTANT_INITIAL_RAISING, CRANE_SPRING_CONSTANT_HANGING );
          m_hCraneTip->m_pSpring->SetSpringConstant( flCurrentSpringConstant );
      }
  
      // If we've moved in any way, update the tip
      if ( m_bDropping || m_flExtensionRate || GetLocalAngularVelocity() != vec3_angle )
      {
          RecalculateCraneTip();
      }
  
      // Make danger sounds underneath the magnet if we have something attached to it
      /*
      if ( (m_flNextDangerSoundTime < gpGlobals->curtime) && (m_hCraneMagnet->GetTotalMassAttachedObjects() > 0) )
      {
          // Trace down from the magnet and make a danger sound on the ground
          trace_t tr;
          Vector vecSource = m_hCraneMagnet->GetAbsOrigin();
          UTIL_TraceLine( vecSource, vecSource - Vector(0,0,2048), MASK_SOLID_BRUSHONLY, m_hCraneMagnet, 0, &tr );
  
          if ( tr.fraction < 1.0 )
          {
              // Make the volume proportional to the amount of mass on the magnet
              float flVolume = clamp( (m_hCraneMagnet->GetTotalMassAttachedObjects() * 0.5), 100.f, 600.f );
              CSoundEnt::InsertSound( SOUND_DANGER, tr.endpos, flVolume, 0.2, this );
  
              //Msg("Total: %.2f Volume: %.2f\n", m_hCraneMagnet->GetTotalMassAttachedObjects(), flVolume );
              //Vector vecVolume = Vector(flVolume,flVolume,flVolume) * 0.5;
              //NDebugOverlay::Box( tr.endpos, -vecVolume, vecVolume, 255,0,0, false, 0.3 );
              //NDebugOverlay::Cross3D( tr.endpos, -Vector(10,10,10), Vector(10,10,10), 255,0,0, false, 0.3 );
          }
  
          m_flNextDangerSoundTime = gpGlobals->curtime + 0.3;
      }
      */
  
      // Play creak sounds on the magnet if there's heavy weight on it
      if ( (m_flNextCreakSound < gpGlobals->curtime) && (m_hCraneMagnet->GetTotalMassAttachedObjects() > 100) )
      {
          // Randomly play creaks from the magnet, and increase the chance based on the turning speed
          float flSpeedPercentage = clamp( fabs(m_flTurn) / m_flMaxTurnSpeed, 0, 1 );
          if ( RandomFloat(0,1) > (0.95 - (0.1 * flSpeedPercentage)) )
          {
              if ( m_ServerVehicle.m_vehicleSounds.iszSound[VS_MISC4] != NULL_STRING )
              {
                  CPASAttenuationFilter filter( m_hCraneMagnet );
  
                  EmitSound_t ep;
                  ep.m_nChannel = CHAN_VOICE;
                  ep.m_pSoundName = STRING(m_ServerVehicle.m_vehicleSounds.iszSound[VS_MISC4]);
                  ep.m_flVolume = 1.0f;
                  ep.m_SoundLevel = SNDLVL_NORM;
  
                  CBaseEntity::EmitSound( filter, m_hCraneMagnet->entindex(), ep );
              }
              m_flNextCreakSound = gpGlobals->curtime + 5.0;
          }
      }
  }
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  void CPropCrane::TurnMagnetOn( void )
  {
      if ( !m_hCraneMagnet->IsOn() )
      {
          variant_t emptyVariant;
          m_hCraneMagnet->AcceptInput( "Toggle", this, this, emptyVariant, USE_TOGGLE );
          m_ServerVehicle.PlaySound( VS_MISC3 );
  
          m_bMagnetOn = true;
      }
  }
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  void CPropCrane::TurnMagnetOff( void )
  {
      if ( m_hCraneMagnet->IsOn() )
      {
          variant_t emptyVariant;
          m_hCraneMagnet->AcceptInput( "Toggle", this, this, emptyVariant, USE_TOGGLE );
          m_ServerVehicle.PlaySound( VS_MISC3 );
  
          m_bMagnetOn = false;
      }
  }
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  const Vector &CPropCrane::GetCraneTipPosition( void )
  {
      return m_hCraneTip->GetAbsOrigin();
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Fills out the values with the desired position of the crane's tip
  //-----------------------------------------------------------------------------
  void CPropCrane::GetCraneTipPosition( Vector *vecOrigin, QAngle *vecAngles )
  {
      GetAttachment( "cable_tip", *vecOrigin, *vecAngles );
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Vehicles are permanently oriented off angle for vphysics.
  //-----------------------------------------------------------------------------
  void CPropCrane::GetVectors(Vector* pForward, Vector* pRight, Vector* pUp) const
  {
      // This call is necessary to cause m_rgflCoordinateFrame to be recomputed
      const matrix3x4_t &entityToWorld = EntityToWorldTransform();
  
      if (pForward != NULL)
      {
          MatrixGetColumn( entityToWorld, 1, *pForward );
      }
  
      if (pRight != NULL)
      {
          MatrixGetColumn( entityToWorld, 0, *pRight );
      }
  
      if (pUp != NULL)
      {
          MatrixGetColumn( entityToWorld, 2, *pUp );
      }
  }
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  CBaseEntity *CPropCrane::GetDriver( void )
  {
      if ( m_hNPCDriver )
          return m_hNPCDriver;
  
      return m_hPlayer;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Prevent the player from entering / exiting the vehicle
  //-----------------------------------------------------------------------------
  void CPropCrane::InputLock( inputdata_t &inputdata )
  {
      m_bLocked = true;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Allow the player to enter / exit the vehicle
  //-----------------------------------------------------------------------------
  void CPropCrane::InputUnlock( inputdata_t &inputdata )
  {
      m_bLocked = false;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose:
  // Input  : &inputdata -
  //-----------------------------------------------------------------------------
  void CPropCrane::InputForcePlayerIn( inputdata_t &inputdata )
  {
      CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 );
      if ( pPlayer && !m_hPlayer )
      {
          GetServerVehicle()->HandlePassengerEntry( pPlayer, 0 );
      }
  }
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  void CPropCrane::SetNPCDriver( CNPC_VehicleDriver *pDriver )
  {
      m_hNPCDriver = pDriver;
      m_nNPCButtons = 0;
  
      if ( pDriver )
      {
          m_flMaxExtensionSpeed = CRANE_EXTENSION_RATE_MAX * 1.5;
          m_flMaxTurnSpeed = CRANE_TURN_RATE_MAX * 1.5;
          m_flExtensionAccel = CRANE_EXTENSION_ACCEL * 2;
          m_flExtensionDecel = CRANE_EXTENSION_DECEL * 20; // Npcs stop quickly to make them more accurate
          m_flTurnAccel = CRANE_TURN_ACCEL * 2;
          m_flTurnDecel = CRANE_DECEL * 10;   // Npcs stop quickly to make them more accurate
  
          // Set our owner entity to be the NPC, so it can path check without hitting us
          SetOwnerEntity( pDriver );
      }
      else
      {
          // Restore player crane speeds
          InitCraneSpeeds();
          SetOwnerEntity( NULL );
  
          // Shutdown the crane's sounds
          m_ServerVehicle.SoundShutdown( 1.0 );
      }
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Allows us to turn off the rumble
  //-----------------------------------------------------------------------------
  void CPropCrane::PreExitVehicle( CBaseCombatCharacter *pPlayer, int nRole )
  {
      if ( pPlayer != m_hPlayer )
          return;
  
      if ( m_hPlayer != NULL )
      {
          // Stop rumbles
          m_hPlayer->RumbleEffect( RUMBLE_FLAT_BOTH, 0, RUMBLE_FLAG_STOP );
      }
  }
  
  //========================================================================================================================================
  // CRANE VEHICLE SERVER VEHICLE
  //========================================================================================================================================
  CPropCrane *CCraneServerVehicle::GetCrane( void )
  {
      return (CPropCrane*)GetDrivableVehicle();
  }
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  void CCraneServerVehicle::GetVehicleViewPosition( int nRole, Vector *pAbsOrigin, QAngle *pAbsAngles, float *pFOV /*= NULL*/ )
  {
      // FIXME: This needs to be reconciled with the other versions of this function!
      Assert( nRole == VEHICLE_ROLE_DRIVER );
      CBasePlayer *pPlayer = ToBasePlayer( GetDrivableVehicle()->GetDriver() );
      Assert( pPlayer );
  
      *pAbsAngles = pPlayer->EyeAngles(); // yuck. this is an in/out parameter.
  
      float flPitchFactor = 1.0;
      matrix3x4_t vehicleEyePosToWorld;
      Vector vehicleEyeOrigin;
      QAngle vehicleEyeAngles;
      GetCrane()->GetAttachment( "vehicle_driver_eyes", vehicleEyeOrigin, vehicleEyeAngles );
      AngleMatrix( vehicleEyeAngles, vehicleEyePosToWorld );
  
      // Compute the relative rotation between the unperterbed eye attachment + the eye angles
      matrix3x4_t cameraToWorld;
      AngleMatrix( *pAbsAngles, cameraToWorld );
  
      matrix3x4_t worldToEyePos;
      MatrixInvert( vehicleEyePosToWorld, worldToEyePos );
  
      matrix3x4_t vehicleCameraToEyePos;
      ConcatTransforms( worldToEyePos, cameraToWorld, vehicleCameraToEyePos );
  
      // Now perterb the attachment point
      vehicleEyeAngles.x = RemapAngleRange( PITCH_CURVE_ZERO * flPitchFactor, PITCH_CURVE_LINEAR, vehicleEyeAngles.x );
      vehicleEyeAngles.z = RemapAngleRange( ROLL_CURVE_ZERO * flPitchFactor, ROLL_CURVE_LINEAR, vehicleEyeAngles.z );
      AngleMatrix( vehicleEyeAngles, vehicleEyeOrigin, vehicleEyePosToWorld );
  
      // Now treat the relative eye angles as being relative to this new, perterbed view position...
      matrix3x4_t newCameraToWorld;
      ConcatTransforms( vehicleEyePosToWorld, vehicleCameraToEyePos, newCameraToWorld );
  
      // output new view abs angles
      MatrixAngles( newCameraToWorld, *pAbsAngles );
  
      // UNDONE: *pOrigin would already be correct in single player if the HandleView() on the server ran after vphysics
      MatrixGetColumn( newCameraToWorld, 3, *pAbsOrigin );
  }
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  void CCraneServerVehicle::NPC_SetDriver( CNPC_VehicleDriver *pDriver )
  {
      GetCrane()->SetNPCDriver( pDriver );
  
      if ( pDriver )
      {
          SetVehicleVolume( 1.0 );    // Vehicles driven by NPCs are louder
          GetCrane()->SetSimulatedEveryTick( false );
      }
      else
      {
          SetVehicleVolume( 0.5 );
          GetCrane()->SetSimulatedEveryTick( true );
      }
  }
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  void CCraneServerVehicle::NPC_DriveVehicle( void )
  {
      if ( g_debug_vehicledriver.GetInt() )
      {
          if ( m_nNPCButtons )
          {
              Vector vecForward, vecRight;
              GetCrane()->GetVectors( &vecForward, &vecRight, NULL );
              if ( m_nNPCButtons & IN_FORWARD )
              {
                  NDebugOverlay::Line( GetCrane()->GetAbsOrigin(), GetCrane()->GetAbsOrigin() + vecForward * 200, 0,255,0, true, 0.1 );
              }
              if ( m_nNPCButtons & IN_BACK )
              {
                  NDebugOverlay::Line( GetCrane()->GetAbsOrigin(), GetCrane()->GetAbsOrigin() - vecForward * 200, 0,255,0, true, 0.1 );
              }
              if ( m_nNPCButtons & IN_MOVELEFT )
              {
                  NDebugOverlay::Line( GetCrane()->GetAbsOrigin(), GetCrane()->GetAbsOrigin() - vecRight * 200, 0,255,0, true, 0.1 );
              }
              if ( m_nNPCButtons & IN_MOVERIGHT )
              {
                  NDebugOverlay::Line( GetCrane()->GetAbsOrigin(), GetCrane()->GetAbsOrigin() + vecRight * 200, 0,255,0, true, 0.1 );
              }
              if ( m_nNPCButtons & IN_JUMP )
              {
                  NDebugOverlay::Box( GetCrane()->GetAbsOrigin(), -Vector(20,20,20), Vector(20,20,20), 0,255,0, true, 0.1 );
              }
          }
      }
  
      GetCrane()->DriveCrane( m_nNPCButtons, m_nNPCButtons, m_flTurnDegrees );
  
      // Clear out attack buttons each frame
      m_nNPCButtons &= ~IN_ATTACK;
      m_nNPCButtons &= ~IN_ATTACK2;
  
      // Run the crane's movement
      GetCrane()->RunCraneMovement( 0.1 );
  }
  
  //===============================================================================================================================
  // CRANE CABLE TIP
  //===============================================================================================================================
  //-----------------------------------------------------------------------------
  // Purpose: To by usable by the constraint system, this needs to have a phys model.
  //-----------------------------------------------------------------------------
  void CCraneTip::Spawn( void )
  {
      Precache();
      SetModel( "models/props_junk/cardboard_box001a.mdl" );
      AddEffects( EF_NODRAW );
  
      // We don't want this to be solid, because we don't want it to collide with the hydra.
      SetSolid( SOLID_VPHYSICS );
      AddSolidFlags( FSOLID_NOT_SOLID );
      VPhysicsInitShadow( false, false );
  
      // Disable movement on this sucker, we're going to move him manually
      SetMoveType( MOVETYPE_NONE );
  
      BaseClass::Spawn();
  
      m_pSpring = NULL;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  void CCraneTip::Precache( void )
  {
      PrecacheModel( "models/props_junk/cardboard_box001a.mdl" );
      BaseClass::Precache();
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Activate/create the constraint
  //-----------------------------------------------------------------------------
  bool CCraneTip::CreateConstraint( CBaseAnimating *pCraneMagnet, IPhysicsConstraintGroup *pGroup )
  {
      IPhysicsObject *pPhysObject = VPhysicsGetObject();
      IPhysicsObject *pCraneMagnetPhysObject = pCraneMagnet->VPhysicsGetObject();
      if ( !pCraneMagnetPhysObject )
      {
          Msg(" Error: Tried to create a crane_tip with a crane magnet that has no physics model.\n" );
          return false;
      }
      Assert( pPhysObject );
  
      // Check to see if it's got an attachment point to connect to
      Vector vecPoint = pCraneMagnet->GetAbsOrigin();
      int iIndex = pCraneMagnet->LookupAttachment("magnetcable_a");
      if ( iIndex >= 0 )
      {
          pCraneMagnet->GetAttachment( iIndex, vecPoint );
      }
  
      // Create our spring
      /*
      constraint_lengthparams_t length;
      length.Defaults();
      length.InitWorldspace( pPhysObject, pCraneMagnetPhysObject, GetAbsOrigin(), vecPoint );
      length.constraint.Defaults();
      m_pConstraint = physenv->CreateLengthConstraint( pPhysObject, pCraneMagnetPhysObject, pGroup, length );
      */
  
      springparams_t spring;
      spring.constant = CRANE_SPRING_CONSTANT_HANGING;
      spring.damping = CRANE_SPRING_DAMPING;
      spring.naturalLength = (GetAbsOrigin() - vecPoint).Length();
      spring.relativeDamping = CRANE_SPRING_RELATIVE_DAMPING;
      spring.startPosition = GetAbsOrigin();
      spring.endPosition = vecPoint;
      spring.useLocalPositions = false;
      spring.onlyStretch = true;
      m_pSpring = physenv->CreateSpring( pPhysObject, pCraneMagnetPhysObject, &spring );
  
      return true;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Create a Hydra Impale between the hydra and the entity passed in
  //-----------------------------------------------------------------------------
  CCraneTip *CCraneTip::Create( CBaseAnimating *pCraneMagnet, IPhysicsConstraintGroup *pGroup, const Vector &vecOrigin, const QAngle &vecAngles )
  {
      CCraneTip *pCraneTip = (CCraneTip *)CBaseEntity::Create( "crane_tip", vecOrigin, vecAngles );
      if ( !pCraneTip )
          return NULL;
  
      if ( !pCraneTip->CreateConstraint( pCraneMagnet, pGroup ) )
          return NULL;
  
      return pCraneTip;
  }
  
  