You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3558 lines
110 KiB
3558 lines
110 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
//============================================================================= |
|
#include "cbase.h" |
|
#include "gamemovement.h" |
|
#include "tf_gamerules.h" |
|
#include "tf_shareddefs.h" |
|
#include "in_buttons.h" |
|
#include "movevars_shared.h" |
|
#include "collisionutils.h" |
|
#include "debugoverlay_shared.h" |
|
#include "baseobject_shared.h" |
|
#include "particle_parse.h" |
|
#include "baseobject_shared.h" |
|
#include "coordsize.h" |
|
#include "tf_weapon_medigun.h" |
|
#include "tf_wearable_item_demoshield.h" |
|
#include "takedamageinfo.h" |
|
#include "tf_weapon_buff_item.h" |
|
#include "halloween/tf_weapon_spellbook.h" |
|
|
|
#ifdef CLIENT_DLL |
|
#include "c_tf_player.h" |
|
#include "c_world.h" |
|
#include "c_team.h" |
|
#include "prediction.h" |
|
|
|
#define CTeam C_Team |
|
|
|
#else |
|
#include "tf_player.h" |
|
#include "team.h" |
|
#include "bot/tf_bot.h" |
|
#include "tf_fx.h" |
|
#endif |
|
|
|
|
|
ConVar tf_duck_debug_spew( "tf_duck_debug_spew", "0", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY ); |
|
ConVar tf_showspeed( "tf_showspeed", "0", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY ); |
|
ConVar tf_avoidteammates( "tf_avoidteammates", "1", FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY, "Controls how teammates interact when colliding.\n 0: Teammates block each other\n 1: Teammates pass through each other, but push each other away (default)" ); |
|
ConVar tf_avoidteammates_pushaway( "tf_avoidteammates_pushaway", "1", FCVAR_REPLICATED, "Whether or not teammates push each other away when occupying the same space" ); |
|
ConVar tf_solidobjects( "tf_solidobjects", "1", FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); |
|
ConVar tf_clamp_back_speed( "tf_clamp_back_speed", "0.9", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY ); |
|
ConVar tf_clamp_back_speed_min( "tf_clamp_back_speed_min", "100", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY ); |
|
ConVar tf_clamp_airducks( "tf_clamp_airducks", "1", FCVAR_REPLICATED ); |
|
ConVar tf_resolve_stuck_players( "tf_resolve_stuck_players", "1", FCVAR_REPLICATED ); |
|
ConVar tf_scout_hype_mod( "tf_scout_hype_mod", "55", FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); |
|
ConVar tf_max_charge_speed( "tf_max_charge_speed", "750", FCVAR_NOTIFY | FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); |
|
ConVar tf_parachute_gravity( "tf_parachute_gravity", "0.2f", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "Gravity while parachute is deployed" ); |
|
ConVar tf_parachute_maxspeed_xy( "tf_parachute_maxspeed_xy", "400.0f", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "Max XY Speed while Parachute is deployed" ); |
|
ConVar tf_parachute_maxspeed_z( "tf_parachute_maxspeed_z", "-100.0f", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "Max Z Speed while Parachute is deployed" ); |
|
ConVar tf_parachute_maxspeed_onfire_z( "tf_parachute_maxspeed_onfire_z", "10.0f", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "Max Z Speed when on Fire and Parachute is deployed" ); |
|
ConVar tf_parachute_aircontrol( "tf_parachute_aircontrol", "2.5f", FCVAR_DEVELOPMENTONLY | FCVAR_REPLICATED, "Multiplier for how much air control players have when Parachute is deployed" ); |
|
|
|
ConVar tf_halloween_kart_aircontrol( "tf_halloween_kart_aircontrol", "1.2f", FCVAR_CHEAT | FCVAR_REPLICATED, "Multiplier for how much air control players have when in Kart Mode" ); |
|
ConVar tf_ghost_up_speed( "tf_ghost_up_speed", "300.f", FCVAR_CHEAT | FCVAR_REPLICATED, "Speed that ghost go upward while holding jump key" ); |
|
ConVar tf_ghost_xy_speed( "tf_ghost_xy_speed", "300.f", FCVAR_CHEAT | FCVAR_REPLICATED ); |
|
ConVar tf_grapplinghook_move_speed( "tf_grapplinghook_move_speed", "750", FCVAR_REPLICATED | FCVAR_CHEAT ); |
|
ConVar tf_grapplinghook_use_acceleration( "tf_grapplinghook_use_acceleration", "0", FCVAR_REPLICATED, "Use full acceleration calculation for grappling hook movement" ); |
|
ConVar tf_grapplinghook_acceleration( "tf_grapplinghook_acceleration", "3500", FCVAR_REPLICATED | FCVAR_CHEAT ); |
|
ConVar tf_grapplinghook_dampening( "tf_grapplinghook_dampening", "500", FCVAR_REPLICATED | FCVAR_CHEAT ); |
|
ConVar tf_grapplinghook_follow_distance( "tf_grapplinghook_follow_distance", "64", FCVAR_REPLICATED | FCVAR_CHEAT ); |
|
ConVar tf_grapplinghook_jump_up_speed( "tf_grapplinghook_jump_up_speed", "375", FCVAR_REPLICATED | FCVAR_CHEAT ); |
|
ConVar tf_grapplinghook_prevent_fall_damage( "tf_grapplinghook_prevent_fall_damage", "0", FCVAR_REPLICATED | FCVAR_CHEAT ); |
|
ConVar tf_grapplinghook_medic_latch_speed_scale( "tf_grapplinghook_medic_latch_speed_scale", "0.65", FCVAR_REPLICATED | FCVAR_CHEAT ); |
|
|
|
#ifdef STAGING_ONLY |
|
ConVar tf_movement_doubletap_window( "tf_movement_doubletap_window", "0.1f", FCVAR_REPLICATED | FCVAR_CHEAT ); |
|
ConVar tf_space_gravity_jump_multipler( "tf_space_gravity_jump_multipler", "1.05", FCVAR_CHEAT | FCVAR_REPLICATED, "Multiplier for player jump velocity in space" ); |
|
ConVar tf_space_aircontrol( "tf_space_aircontrol", "1.0", FCVAR_CHEAT | FCVAR_REPLICATED, "Multiplier for how much air control players have in space" ); |
|
|
|
ConVar tf_taunt_move_speed( "tf_taunt_move_speed", "0", FCVAR_REPLICATED | FCVAR_CHEAT ); |
|
#endif // STAGING_ONLY |
|
|
|
extern ConVar cl_forwardspeed; |
|
extern ConVar cl_backspeed; |
|
extern ConVar cl_sidespeed; |
|
extern ConVar mp_tournament_readymode_countdown; |
|
|
|
#define TF_MAX_SPEED (400 * 1.3) // 400 is Scout max speed, and we allow up to 3% movement bonus. |
|
|
|
#define TF_WATERJUMP_FORWARD 30 |
|
#define TF_WATERJUMP_UP 300 |
|
#define TF_TIME_TO_DUCK 0.3f |
|
#define TF_AIRDUCKED_COUNT 2 |
|
//ConVar tf_waterjump_up( "tf_waterjump_up", "300", FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); |
|
//ConVar tf_waterjump_forward( "tf_waterjump_forward", "30", FCVAR_REPLICATED | FCVAR_CHEAT | FCVAR_DEVELOPMENTONLY ); |
|
|
|
#define NUM_CROUCH_HINTS 3 |
|
|
|
class CTFGameMovement : public CGameMovement |
|
{ |
|
public: |
|
DECLARE_CLASS( CTFGameMovement, CGameMovement ); |
|
|
|
CTFGameMovement(); |
|
|
|
virtual void PlayerMove(); |
|
virtual unsigned int PlayerSolidMask( bool brushOnly = false ); |
|
virtual void ProcessMovement( CBasePlayer *pBasePlayer, CMoveData *pMove ); |
|
virtual bool CanAccelerate(); |
|
virtual bool CheckJumpButton(); |
|
virtual int CheckStuck( void ); |
|
virtual bool CheckWater( void ); |
|
virtual void WaterMove( void ); |
|
virtual void FullWalkMove(); |
|
virtual void WalkMove( void ); |
|
virtual void AirMove( void ); |
|
virtual void FullTossMove( void ); |
|
virtual void CategorizePosition( void ); |
|
virtual void CheckFalling( void ); |
|
virtual void Duck( void ); |
|
virtual Vector GetPlayerViewOffset( bool ducked ) const; |
|
bool GrapplingHookMove( void ); |
|
bool ChargeMove( void ); |
|
bool StunMove( void ); |
|
bool TauntMove( void ); |
|
void VehicleMove( void ); |
|
bool HighMaxSpeedMove( void ); |
|
virtual float GetAirSpeedCap( void ); |
|
|
|
virtual void TracePlayerBBox( const Vector& start, const Vector& end, unsigned int fMask, int collisionGroup, trace_t& pm ); |
|
virtual CBaseHandle TestPlayerPosition( const Vector& pos, int collisionGroup, trace_t& pm ); |
|
virtual void StepMove( Vector &vecDestination, trace_t &trace ); |
|
virtual bool GameHasLadders() const; |
|
virtual void SetGroundEntity( trace_t *pm ); |
|
virtual void PlayerRoughLandingEffects( float fvol ); |
|
|
|
virtual void HandleDuckingSpeedCrop( void ); |
|
protected: |
|
|
|
virtual void CheckWaterJump( void ); |
|
void FullWalkMoveUnderwater(); |
|
|
|
private: |
|
|
|
bool CheckWaterJumpButton( void ); |
|
void AirDash( void ); |
|
void PreventBunnyJumping(); |
|
void ToggleParachute( void ); |
|
void CheckKartWallBumping(); |
|
|
|
// Ducking. |
|
#if 0 |
|
// New duck tests! |
|
void HandleDuck( int nButtonsPressed ); |
|
void HandleUnDuck( int nButtonsReleased ); |
|
void TestDuck(); |
|
#endif |
|
void DuckOverrides(); |
|
void OnDuck( int nButtonsPressed ); |
|
void OnUnDuck( int nButtonsReleased ); |
|
|
|
#ifdef STAGING_ONLY |
|
void CheckForDoubleTap( void ); |
|
void OnDoubleTapped( int nKey ); |
|
void TeleportMove( Vector &vecDirection, float flDist ); |
|
|
|
CUtlMap< int, float > m_MoveKeyDownTimes; |
|
float m_flNextDoubleTapTeleportTime; |
|
#endif // STAGING_ONLY |
|
|
|
private: |
|
|
|
Vector m_vecWaterPoint; |
|
CTFPlayer *m_pTFPlayer; |
|
bool m_isPassingThroughEnemies; |
|
|
|
static float CalcWishSpeedThreshold() |
|
{ |
|
return 100.0f * sv_friction.GetFloat() / (sv_accelerate.GetFloat()); |
|
} |
|
}; |
|
|
|
|
|
// Expose our interface. |
|
static CTFGameMovement g_GameMovement; |
|
IGameMovement *g_pGameMovement = ( IGameMovement * )&g_GameMovement; |
|
|
|
EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CGameMovement, IGameMovement,INTERFACENAME_GAMEMOVEMENT, g_GameMovement ); |
|
|
|
|
|
// ---------------------------------------------------------------------------------------- // |
|
// CTFGameMovement. |
|
// ---------------------------------------------------------------------------------------- // |
|
|
|
CTFGameMovement::CTFGameMovement() |
|
{ |
|
m_pTFPlayer = NULL; |
|
m_isPassingThroughEnemies = false; |
|
|
|
#ifdef STAGING_ONLY |
|
m_MoveKeyDownTimes.SetLessFunc( DefLessFunc (int) ); |
|
m_flNextDoubleTapTeleportTime = 0.f; |
|
#endif // STAGING_ONLY |
|
} |
|
|
|
//---------------------------------------------------------------------------------------- |
|
// Purpose: moves the player |
|
//---------------------------------------------------------------------------------------- |
|
void CTFGameMovement::PlayerMove() |
|
{ |
|
// call base class to do movement |
|
BaseClass::PlayerMove(); |
|
|
|
// handle player's interaction with water |
|
int nNewWaterLevel = m_pTFPlayer->GetWaterLevel(); |
|
if ( m_nOldWaterLevel != nNewWaterLevel ) |
|
{ |
|
if ( WL_NotInWater == m_nOldWaterLevel ) |
|
{ |
|
// The player has just entered the water. Determine if we should play a splash sound. |
|
bool bPlaySplash = false; |
|
|
|
Vector vecVelocity = m_pTFPlayer->GetAbsVelocity(); |
|
if ( vecVelocity.z <= -200.0f ) |
|
{ |
|
// If the player has significant downward velocity, play a splash regardless of water depth. (e.g. Jumping hard into a puddle) |
|
bPlaySplash = true; |
|
} |
|
else |
|
{ |
|
// Look at the water depth below the player. If it's significantly deep, play a splash to accompany the sinking that's about to happen. |
|
Vector vecStart = m_pTFPlayer->GetAbsOrigin(); |
|
Vector vecEnd = vecStart; |
|
vecEnd.z -= 20; // roughly thigh deep |
|
trace_t tr; |
|
// see if we hit anything solid a little bit below the player |
|
UTIL_TraceLine( vecStart, vecEnd, MASK_SOLID,m_pTFPlayer, COLLISION_GROUP_NONE, &tr ); |
|
if ( tr.fraction >= 1.0f ) |
|
{ |
|
// some amount of water below the player, play a splash |
|
bPlaySplash = true; |
|
} |
|
} |
|
|
|
if ( bPlaySplash ) |
|
{ |
|
m_pTFPlayer->EmitSound( "Physics.WaterSplash" ); |
|
} |
|
} |
|
} |
|
|
|
// Remove our shield charge if we slow down a bunch. |
|
float flSpeed = VectorLength( mv->m_vecVelocity ); |
|
if ( flSpeed < 300.0f ) |
|
{ |
|
m_pTFPlayer->m_Shared.EndCharge(); |
|
} |
|
} |
|
|
|
Vector CTFGameMovement::GetPlayerViewOffset( bool ducked ) const |
|
{ |
|
return ( ( ducked ) ? ( VEC_DUCK_VIEW_SCALED( m_pTFPlayer ) ) : ( m_pTFPlayer->GetClassEyeHeight() ) ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Allow bots etc to use slightly different solid masks |
|
//----------------------------------------------------------------------------- |
|
unsigned int CTFGameMovement::PlayerSolidMask( bool brushOnly ) |
|
{ |
|
unsigned int uMask = 0; |
|
|
|
// Ghost players dont collide with anything but the world |
|
if ( m_pTFPlayer && m_pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) ) |
|
{ |
|
return MASK_PLAYERSOLID_BRUSHONLY; |
|
} |
|
|
|
if ( m_pTFPlayer && !m_isPassingThroughEnemies ) |
|
{ |
|
switch( m_pTFPlayer->GetTeamNumber() ) |
|
{ |
|
case TF_TEAM_RED: |
|
uMask = CONTENTS_BLUETEAM; |
|
break; |
|
|
|
case TF_TEAM_BLUE: |
|
uMask = CONTENTS_REDTEAM; |
|
break; |
|
} |
|
} |
|
|
|
return ( uMask | BaseClass::PlayerSolidMask( brushOnly ) ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Overridden to allow players to run faster than the maxspeed |
|
//----------------------------------------------------------------------------- |
|
void CTFGameMovement::ProcessMovement( CBasePlayer *pBasePlayer, CMoveData *pMove ) |
|
{ |
|
// Verify data. |
|
Assert( pBasePlayer ); |
|
Assert( pMove ); |
|
if ( !pBasePlayer || !pMove ) |
|
return; |
|
|
|
// Reset point contents for water check. |
|
ResetGetPointContentsCache(); |
|
|
|
// Cropping movement speed scales mv->m_fForwardSpeed etc. globally |
|
// Once we crop, we don't want to recursively crop again, so we set the crop |
|
// flag globally here once per usercmd cycle. |
|
m_iSpeedCropped = SPEED_CROPPED_RESET; |
|
|
|
// Get the current TF player. |
|
m_pTFPlayer = ToTFPlayer( pBasePlayer ); |
|
player = m_pTFPlayer; |
|
mv = pMove; |
|
|
|
// The max speed is currently set to the scout - if this changes we need to change this! |
|
mv->m_flMaxSpeed = TF_MAX_SPEED; |
|
|
|
// Handle charging demomens |
|
ChargeMove(); |
|
|
|
// Handle player stun. |
|
StunMove(); |
|
|
|
// Handle player taunt move |
|
TauntMove(); |
|
|
|
// Handle grappling hook move |
|
GrapplingHookMove(); |
|
|
|
// Handle scouts that can move really fast with buffs |
|
HighMaxSpeedMove(); |
|
|
|
// Run the command. |
|
PlayerMove(); |
|
|
|
#ifdef STAGING_ONLY |
|
CheckForDoubleTap(); |
|
#endif // STAGING_ONLY |
|
|
|
FinishMove(); |
|
|
|
#if defined(GAME_DLL) |
|
m_pTFPlayer->m_bTakenBlastDamageSinceLastMovement = false; |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CTFGameMovement::GrapplingHookMove() |
|
{ |
|
CBaseEntity *pHookTarget = m_pTFPlayer->GetGrapplingHookTarget(); |
|
|
|
if ( !pHookTarget ) |
|
return false; |
|
|
|
// check if player can be moved |
|
if ( !m_pTFPlayer->CanPlayerMove() || m_pTFPlayer->m_Shared.IsControlStunned() ) |
|
{ |
|
mv->m_flForwardMove = 0.f; |
|
mv->m_flSideMove = 0.f; |
|
mv->m_flUpMove = 0.f; |
|
mv->m_nButtons = 0; |
|
m_pTFPlayer->m_nButtons = mv->m_nButtons; |
|
return false; |
|
} |
|
|
|
m_pTFPlayer->SetGroundEntity( NULL ); |
|
|
|
Vector vDesiredMove = pHookTarget->WorldSpaceCenter() - m_pTFPlayer->WorldSpaceCenter(); |
|
|
|
CTFPlayer *pPlayerToCheckForRune = m_pTFPlayer; |
|
if ( pHookTarget->IsPlayer() ) |
|
{ |
|
CTFPlayer *pHookedPlayer = ToTFPlayer( pHookTarget ); |
|
bool bFollowingAllyGrapple = false; |
|
// If our target is grappling, adjust aim to behind them |
|
CBaseEntity *pHookedPlayerTarget = pHookedPlayer->GetGrapplingHookTarget(); |
|
if ( pHookedPlayerTarget ) |
|
{ |
|
bFollowingAllyGrapple = pHookedPlayer->GetTeamNumber() == m_pTFPlayer->GetTeamNumber(); |
|
Vector vTargetGrapple = pHookedPlayerTarget->WorldSpaceCenter() - pHookedPlayer->WorldSpaceCenter(); |
|
vTargetGrapple.NormalizeInPlace(); |
|
vDesiredMove += vTargetGrapple * ( -1 * tf_grapplinghook_follow_distance.GetFloat() ); |
|
} |
|
else |
|
{ |
|
// Otherwise, aim short of their center. |
|
vDesiredMove += vDesiredMove.Normalized() * ( -1 * tf_grapplinghook_follow_distance.GetFloat() ); |
|
} |
|
|
|
if ( bFollowingAllyGrapple ) |
|
{ |
|
pPlayerToCheckForRune = pHookedPlayer; |
|
} |
|
} |
|
|
|
mv->m_flMaxSpeed = tf_grapplinghook_move_speed.GetFloat(); |
|
|
|
// If we're grappling along with an ally, use their rune to avoid falling behind or passing them |
|
if ( pPlayerToCheckForRune->m_Shared.GetCarryingRuneType() == RUNE_AGILITY ) |
|
{ |
|
mv->m_flMaxSpeed = 950.f; |
|
} |
|
// Heavies get a grapple speed reduction across the board, even if they have Agility |
|
if ( pPlayerToCheckForRune->GetPlayerClass()->GetClassIndex() == TF_CLASS_HEAVYWEAPONS ) |
|
{ |
|
mv->m_flMaxSpeed *= 0.70f; |
|
} |
|
// Grapple movement speed penalty if player is carrying the flag and a powerup |
|
else if ( pPlayerToCheckForRune->HasTheFlag() && pPlayerToCheckForRune->m_Shared.GetCarryingRuneType() != RUNE_NONE ) |
|
{ |
|
if ( pPlayerToCheckForRune->m_Shared.GetCarryingRuneType() == RUNE_AGILITY ) |
|
{ |
|
mv->m_flMaxSpeed *= 0.8f; |
|
} |
|
else |
|
{ |
|
mv->m_flMaxSpeed *= 0.65f; |
|
} |
|
} |
|
// Pyros that are hooked into enemy players travel slower because of their advantage in close quarters |
|
else if ( pPlayerToCheckForRune->GetPlayerClass()->GetClassIndex() == TF_CLASS_PYRO && pPlayerToCheckForRune->m_Shared.InCond( TF_COND_GRAPPLED_TO_PLAYER ) ) |
|
{ |
|
mv->m_flMaxSpeed *= 0.7f; |
|
} |
|
|
|
// if the medic hook latched on to teammate, his movement should be slower to eventually detach from the healing target |
|
// this requires medic to do something instead of getting a free ride (except medic with AGILITY rune) |
|
if ( m_pTFPlayer->GetPlayerClass()->GetClassIndex() == TF_CLASS_MEDIC && pHookTarget->IsPlayer() && pHookTarget->InSameTeam( m_pTFPlayer ) && m_pTFPlayer->m_Shared.GetCarryingRuneType() != RUNE_AGILITY ) |
|
{ |
|
mv->m_flMaxSpeed *= tf_grapplinghook_medic_latch_speed_scale.GetFloat(); |
|
} |
|
|
|
if ( tf_grapplinghook_use_acceleration.GetBool() ) |
|
{ |
|
// Use acceleration with dampening |
|
float flSpeed = mv->m_vecVelocity.Length(); |
|
if ( flSpeed > 0.f ) { |
|
float flDampen = Min( tf_grapplinghook_dampening.GetFloat() * gpGlobals->frametime, flSpeed ); |
|
mv->m_vecVelocity *= ( flSpeed - flDampen ) / flSpeed; |
|
} |
|
|
|
mv->m_vecVelocity += vDesiredMove.Normalized() * ( tf_grapplinghook_acceleration.GetFloat() * gpGlobals->frametime ); |
|
|
|
flSpeed = mv->m_vecVelocity.Length(); |
|
if ( flSpeed > mv->m_flMaxSpeed ) |
|
{ |
|
mv->m_vecVelocity *= mv->m_flMaxSpeed / flSpeed; |
|
} |
|
} |
|
else |
|
{ |
|
// Simple velocity calculation |
|
float vDist = vDesiredMove.Length(); |
|
if ( vDist > mv->m_flMaxSpeed * gpGlobals->frametime ) |
|
{ |
|
mv->m_vecVelocity = vDesiredMove * ( mv->m_flMaxSpeed / vDist ); |
|
} |
|
else |
|
{ |
|
mv->m_vecVelocity = vDesiredMove / gpGlobals->frametime; |
|
} |
|
} |
|
|
|
// slow down when player is close to the hook target to prevent yoyo effect |
|
float flDistSqrToTarget = m_pTFPlayer->GetAbsOrigin().DistToSqr( pHookTarget->GetAbsOrigin() ); |
|
if ( flDistSqrToTarget < 10000 ) |
|
{ |
|
// remap the speed between 80-100 unit distance |
|
mv->m_vecVelocity = mv->m_vecVelocity.Normalized() * RemapValClamped( flDistSqrToTarget, 6400, 10000, 0.f, mv->m_flMaxSpeed ); |
|
} |
|
|
|
mv->m_flForwardMove = 0.f; |
|
mv->m_flSideMove = 0.f; |
|
mv->m_flUpMove = 0.f; |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CTFGameMovement::ChargeMove() |
|
{ |
|
if ( !m_pTFPlayer->m_Shared.InCond( TF_COND_SHIELD_CHARGE ) ) |
|
{ |
|
// Check for Quick Fix Medic healing a charging player |
|
if ( !m_pTFPlayer->IsPlayerClass( TF_CLASS_MEDIC ) ) |
|
return false; |
|
|
|
CTFWeaponBase *pTFWeapon = m_pTFPlayer->GetActiveTFWeapon(); |
|
if ( !pTFWeapon ) |
|
return false; |
|
|
|
if ( pTFWeapon->GetWeaponID() != TF_WEAPON_MEDIGUN ) |
|
return false; |
|
|
|
CWeaponMedigun *pMedigun = static_cast< CWeaponMedigun* >( pTFWeapon ); |
|
if ( !pMedigun || pMedigun->GetMedigunType() != MEDIGUN_QUICKFIX ) |
|
return false; |
|
|
|
CTFPlayer *pHealTarget = ToTFPlayer( pMedigun->GetHealTarget() ); |
|
if ( !pHealTarget || !pHealTarget->m_Shared.InCond( TF_COND_SHIELD_CHARGE ) ) |
|
return false; |
|
} |
|
|
|
mv->m_flMaxSpeed = tf_max_charge_speed.GetFloat(); |
|
|
|
int oldbuttons = mv->m_nButtons; |
|
|
|
// Handle demoman shield charge. |
|
mv->m_flForwardMove = tf_max_charge_speed.GetFloat(); |
|
mv->m_flSideMove = 0.0f; |
|
mv->m_flUpMove = 0.0f; |
|
if ( mv->m_nButtons & IN_ATTACK2 ) |
|
{ |
|
// Allow the player to continue to hold alt-fire. |
|
mv->m_nButtons = IN_ATTACK2; |
|
} |
|
else |
|
{ |
|
mv->m_nButtons = 0; |
|
} |
|
|
|
if ( oldbuttons & IN_ATTACK ) |
|
{ |
|
mv->m_nButtons |= IN_ATTACK; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CTFGameMovement::StunMove() |
|
{ |
|
// Handle control stun. |
|
if ( m_pTFPlayer->m_Shared.IsControlStunned() |
|
|| m_pTFPlayer->m_Shared.IsLoserStateStunned() ) |
|
{ |
|
// Can't fire or select weapons. |
|
if ( m_pTFPlayer->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) ) |
|
{ |
|
// Heavies can still spin their gun. |
|
if ( mv->m_nButtons & IN_ATTACK2 || mv->m_nButtons & IN_ATTACK ) |
|
{ |
|
mv->m_nButtons = IN_ATTACK2; // Turn off all other buttons. |
|
} |
|
} |
|
else |
|
{ |
|
mv->m_nButtons = 0; |
|
} |
|
|
|
if ( m_pTFPlayer->m_Shared.IsControlStunned() ) |
|
{ |
|
mv->m_flForwardMove = 0.0f; |
|
mv->m_flSideMove = 0.0f; |
|
mv->m_flUpMove = 0.0f; |
|
} |
|
|
|
m_pTFPlayer->m_nButtons = mv->m_nButtons; |
|
} |
|
|
|
// Handle movement stuns |
|
float flStunAmount = m_pTFPlayer->m_Shared.GetAmountStunned( TF_STUN_MOVEMENT ); |
|
// Lerp to the desired amount |
|
if ( flStunAmount ) |
|
{ |
|
if ( m_pTFPlayer->m_Shared.m_flStunLerpTarget != flStunAmount ) |
|
{ |
|
m_pTFPlayer->m_Shared.m_flLastMovementStunChange = gpGlobals->curtime; |
|
m_pTFPlayer->m_Shared.m_flStunLerpTarget = flStunAmount; |
|
m_pTFPlayer->m_Shared.m_bStunNeedsFadeOut = true; |
|
} |
|
|
|
mv->m_flForwardMove *= 1.f - flStunAmount; |
|
mv->m_flSideMove *= 1.f - flStunAmount; |
|
if ( m_pTFPlayer->m_Shared.GetStunFlags() & TF_STUN_MOVEMENT_FORWARD_ONLY ) |
|
{ |
|
mv->m_flForwardMove = 0.f; |
|
} |
|
|
|
return true; |
|
} |
|
else if ( m_pTFPlayer->m_Shared.m_flLastMovementStunChange ) |
|
{ |
|
// Lerp out to normal speed |
|
if ( m_pTFPlayer->m_Shared.m_bStunNeedsFadeOut ) |
|
{ |
|
m_pTFPlayer->m_Shared.m_flLastMovementStunChange = gpGlobals->curtime; |
|
m_pTFPlayer->m_Shared.m_bStunNeedsFadeOut = false; |
|
} |
|
|
|
float flCurStun = RemapValClamped( (gpGlobals->curtime - m_pTFPlayer->m_Shared.m_flLastMovementStunChange), 0.2, 0.0, 0.0, 1.0 ); |
|
if ( flCurStun ) |
|
{ |
|
float flRemap = m_pTFPlayer->m_Shared.m_flStunLerpTarget * flCurStun; |
|
mv->m_flForwardMove *= (1.0 - flRemap); |
|
mv->m_flSideMove *= (1.0 - flRemap); |
|
if ( m_pTFPlayer->m_Shared.GetStunFlags() & TF_STUN_MOVEMENT_FORWARD_ONLY ) |
|
{ |
|
mv->m_flForwardMove = 0.f; |
|
} |
|
} |
|
else |
|
{ |
|
m_pTFPlayer->m_Shared.m_flStunLerpTarget = 0.f; |
|
m_pTFPlayer->m_Shared.m_flLastMovementStunChange = 0; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
// No one can move when in a final countdown transition. |
|
// Do this here to avoid the inevitable hack that prevents players |
|
// from receiving a flag or condition by stalling thinks, etc. |
|
if ( TFGameRules() && TFGameRules()->BInMatchStartCountdown() ) |
|
{ |
|
mv->m_flForwardMove = 0.f; |
|
mv->m_flSideMove = 0.f; |
|
mv->m_flUpMove = 0.f; |
|
mv->m_nButtons = 0; |
|
m_pTFPlayer->m_nButtons = mv->m_nButtons; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CTFGameMovement::TauntMove( void ) |
|
{ |
|
if ( m_pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) ) |
|
{ |
|
VehicleMove(); |
|
} |
|
else if ( m_pTFPlayer->m_Shared.InCond( TF_COND_TAUNTING ) && m_pTFPlayer->CanMoveDuringTaunt() ) |
|
{ |
|
m_pTFPlayer->SetTauntYaw( mv->m_vecViewAngles[YAW] ); |
|
|
|
bool bForceMoveForward = m_pTFPlayer->IsTauntForceMovingForward(); |
|
float flMaxMoveSpeed = m_pTFPlayer->GetTauntMoveSpeed(); |
|
float flAcceleration = m_pTFPlayer->GetTauntMoveAcceleration(); |
|
|
|
float flMoveDir = 0.f; |
|
if ( !bForceMoveForward ) |
|
{ |
|
// Grab analog inputs, normalized to [0,1], to allow controller to also drive taunt movement. |
|
if ( mv->m_flForwardMove > 0 && cl_forwardspeed.GetFloat() > 0 ) |
|
{ |
|
flMoveDir += mv->m_flForwardMove / cl_forwardspeed.GetFloat(); |
|
} |
|
else if ( mv->m_flForwardMove < 0 && cl_backspeed.GetFloat() > 0 ) |
|
{ |
|
flMoveDir += mv->m_flForwardMove / cl_backspeed.GetFloat(); |
|
} |
|
|
|
// No need to read buttons explicitly anymore, since that input is already included in m_flForwardMove |
|
/* if ( mv->m_nButtons & IN_FORWARD ) |
|
flMoveDir += 1.f; |
|
if ( mv->m_nButtons & IN_BACK ) |
|
flMoveDir += -1.f; */ |
|
|
|
// Clamp to [0,1], just in case. |
|
if ( flMoveDir > 1.0f ) |
|
{ |
|
flMoveDir = 1.0f; |
|
} |
|
else if ( flMoveDir < -1.0f ) |
|
{ |
|
flMoveDir = -1.0f; |
|
} |
|
} |
|
else |
|
{ |
|
flMoveDir = 1.f; |
|
} |
|
|
|
bool bMoving = flMoveDir != 0.f; |
|
float flSign = bMoving ? 1.f : -1.f; |
|
#ifdef STAGING_ONLY |
|
flMaxMoveSpeed = tf_taunt_move_speed.GetFloat() > 0.f ? tf_taunt_move_speed.GetFloat() : flMaxMoveSpeed; |
|
#endif // STAGING_ONLY |
|
if ( flAcceleration > 0.f ) |
|
{ |
|
m_pTFPlayer->SetCurrentTauntMoveSpeed( clamp( m_pTFPlayer->GetCurrentTauntMoveSpeed() + flSign * ( gpGlobals->frametime / flAcceleration ) * flMaxMoveSpeed, 0.f, flMaxMoveSpeed ) ); |
|
} |
|
else |
|
{ |
|
m_pTFPlayer->SetCurrentTauntMoveSpeed( flMaxMoveSpeed ); |
|
} |
|
|
|
// don't allow taunt to move if the player cannot move |
|
if ( !m_pTFPlayer->CanPlayerMove() ) |
|
{ |
|
flMaxMoveSpeed = 0.f; |
|
} |
|
|
|
float flSmoothMoveSpeed = 0.f; |
|
if ( flMaxMoveSpeed > 0.f ) |
|
{ |
|
flSmoothMoveSpeed = SimpleSpline( m_pTFPlayer->GetCurrentTauntMoveSpeed() / flMaxMoveSpeed ) * flMaxMoveSpeed; |
|
} |
|
|
|
mv->m_flMaxSpeed = flMaxMoveSpeed; |
|
mv->m_flForwardMove = flMoveDir * flSmoothMoveSpeed; |
|
mv->m_flClientMaxSpeed = flMaxMoveSpeed; |
|
|
|
return true; |
|
} |
|
else |
|
{ |
|
m_pTFPlayer->SetCurrentTauntMoveSpeed( 0.f ); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
ConVar tf_halloween_kart_dash_speed( "tf_halloween_kart_dash_speed", "1000", FCVAR_CHEAT | FCVAR_REPLICATED ); |
|
ConVar tf_halloween_kart_dash_accel( "tf_halloween_kart_dash_accel", "750", FCVAR_CHEAT | FCVAR_REPLICATED ); |
|
|
|
ConVar tf_halloween_kart_normal_speed( "tf_halloween_kart_normal_speed", "650", FCVAR_CHEAT | FCVAR_REPLICATED ); |
|
ConVar tf_halloween_kart_normal_accel( "tf_halloween_kart_normal_accel", "300", FCVAR_CHEAT | FCVAR_REPLICATED ); |
|
ConVar tf_halloween_kart_slowmoving_accel( "tf_halloween_kart_slowmoving_accel", "500", FCVAR_CHEAT | FCVAR_REPLICATED ); |
|
ConVar tf_halloween_kart_slowmoving_threshold( "tf_halloween_kart_slowmoving_threshold", "300", FCVAR_CHEAT | FCVAR_REPLICATED ); |
|
|
|
ConVar tf_halloween_kart_reverse_speed( "tf_halloween_kart_reverse_speed", "-50", FCVAR_CHEAT | FCVAR_REPLICATED ); |
|
ConVar tf_halloween_kart_brake_speed( "tf_halloween_kart_brake_speed", "0", FCVAR_CHEAT | FCVAR_REPLICATED ); |
|
ConVar tf_halloween_kart_brake_accel( "tf_halloween_kart_brake_accel", "500", FCVAR_CHEAT | FCVAR_REPLICATED ); |
|
|
|
ConVar tf_halloween_kart_idle_speed( "tf_halloween_kart_idle_speed", "0", FCVAR_CHEAT | FCVAR_REPLICATED ); |
|
ConVar tf_halloween_kart_coast_accel( "tf_halloween_kart_coast_accel", "300", FCVAR_CHEAT | FCVAR_REPLICATED ); |
|
|
|
ConVar tf_halloween_kart_bombhead_scale( "tf_halloween_kart_bombhead_scale", "1.5f", FCVAR_CHEAT | FCVAR_REPLICATED ); |
|
void CTFGameMovement::VehicleMove( void ) |
|
{ |
|
// Reset Flags |
|
m_pTFPlayer->m_iKartState = 0; |
|
m_pTFPlayer->SetTauntYaw( mv->m_vecViewAngles[YAW] ); |
|
|
|
float flMaxMoveSpeed = tf_halloween_kart_normal_speed.GetFloat(); |
|
|
|
float flTargetSpeed = tf_halloween_kart_idle_speed.GetFloat(); |
|
// Just standard accell by default |
|
float flAcceleration = tf_halloween_kart_coast_accel.GetFloat(); |
|
|
|
bool bInput = false; |
|
|
|
// Hitting the gas |
|
if ( mv->m_flForwardMove > 0.0f ) |
|
{ |
|
// Grab normalized analog input (no need to check key input explicitly, since it's already baked into m_flForwardMove |
|
float flNormalizedForwardInput = cl_forwardspeed.GetFloat() > 0.0f ? mv->m_flForwardMove / cl_forwardspeed.GetFloat() : 0.0f; |
|
if ( flNormalizedForwardInput > 1.0f ) |
|
{ |
|
flNormalizedForwardInput = 1.0f; |
|
} |
|
|
|
// Target normal speed |
|
flTargetSpeed = tf_halloween_kart_normal_speed.GetFloat(); |
|
// Use normal accell speed if it's faster than our current speed |
|
if ( flTargetSpeed > m_pTFPlayer->GetCurrentTauntMoveSpeed() ) |
|
{ |
|
if ( m_pTFPlayer->GetCurrentTauntMoveSpeed() < tf_halloween_kart_slowmoving_threshold.GetFloat() ) |
|
{ |
|
flAcceleration = tf_halloween_kart_slowmoving_accel.GetFloat() * flNormalizedForwardInput; |
|
} |
|
else |
|
{ |
|
flAcceleration = tf_halloween_kart_normal_accel.GetFloat() * flNormalizedForwardInput; |
|
} |
|
} |
|
|
|
bInput = true; |
|
m_pTFPlayer->m_iKartState |= CTFPlayerShared::kKartState_Driving; |
|
} |
|
else if ( mv->m_flForwardMove < 0.0f ) // Hitting the brakes |
|
{ |
|
// Grab normalized analog input (no need to check key input explicitly, since it's already baked into m_flForwardMove. And flip the sign, since we're going backwards. |
|
float flNormalizedForwardInput = cl_backspeed.GetFloat() > 0.0f ? mv->m_flForwardMove / cl_backspeed.GetFloat() : 0.0f; |
|
if ( flNormalizedForwardInput < -1.0f ) |
|
{ |
|
flNormalizedForwardInput = 1.0f; |
|
} |
|
else |
|
{ |
|
flNormalizedForwardInput = -flNormalizedForwardInput; |
|
} |
|
|
|
// slowing down |
|
if ( m_pTFPlayer->GetCurrentTauntMoveSpeed() > 0 ) |
|
{ |
|
// Target brake speed |
|
flTargetSpeed = tf_halloween_kart_brake_speed.GetFloat(); |
|
// Use brake accell speed if it's slower than our current speed |
|
if ( flTargetSpeed < m_pTFPlayer->GetCurrentTauntMoveSpeed() ) |
|
{ |
|
flAcceleration = tf_halloween_kart_brake_accel.GetFloat() * flNormalizedForwardInput; |
|
} |
|
m_pTFPlayer->m_iKartState |= CTFPlayerShared::kKartState_Braking; |
|
} |
|
// if we are already stopped, look for new input to start going backwards |
|
else |
|
{ |
|
// check for new input, else do nothing |
|
if ( mv->m_flOldForwardMove >= 0.0f || m_pTFPlayer->GetCurrentTauntMoveSpeed() < 0 || m_pTFPlayer->GetVehicleReverseTime() < gpGlobals->curtime ) |
|
{ |
|
// going backwards, keep going backwards |
|
flTargetSpeed = tf_halloween_kart_reverse_speed.GetFloat(); |
|
// Use brake accell speed if it's slower than our current speed |
|
if ( flTargetSpeed < m_pTFPlayer->GetCurrentTauntMoveSpeed() ) |
|
{ |
|
flAcceleration = tf_halloween_kart_brake_accel.GetFloat() * flNormalizedForwardInput; |
|
} |
|
m_pTFPlayer->m_iKartState |= CTFPlayerShared::kKartState_Reversing; |
|
} |
|
else |
|
{ |
|
// Stall for 1 second then start reversing |
|
if ( m_pTFPlayer->GetVehicleReverseTime() == FLT_MAX ) |
|
{ |
|
m_pTFPlayer->SetVehicleReverseTime( gpGlobals->curtime + 0.6f ); |
|
} |
|
m_pTFPlayer->m_iKartState |= CTFPlayerShared::kKartState_Stopped; |
|
} |
|
} |
|
|
|
bInput = true; |
|
} |
|
|
|
if ( m_pTFPlayer->GetCurrentTauntMoveSpeed() > 0 ) |
|
{ |
|
m_pTFPlayer->SetVehicleReverseTime( FLT_MAX ); |
|
} |
|
|
|
// braking? |
|
if ( bInput && Sign( m_pTFPlayer->GetCurrentTauntMoveSpeed() ) != Sign( flTargetSpeed ) ) |
|
{ |
|
flAcceleration = tf_halloween_kart_brake_accel.GetFloat(); |
|
} |
|
|
|
if ( m_pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_BOMB_HEAD ) ) |
|
{ |
|
flMaxMoveSpeed *= tf_halloween_kart_bombhead_scale.GetFloat(); |
|
flAcceleration *= tf_halloween_kart_bombhead_scale.GetFloat(); |
|
} |
|
|
|
float flTargetMoveSpeed = Approach( flTargetSpeed, m_pTFPlayer->GetCurrentTauntMoveSpeed(), flAcceleration * gpGlobals->frametime ); |
|
float flSmoothMoveSpeed = Bias( fabs( m_pTFPlayer->GetCurrentTauntMoveSpeed() ) / flMaxMoveSpeed, 0.7f ) * flMaxMoveSpeed * Sign( flTargetMoveSpeed ); |
|
|
|
// Boost slams the accelerator |
|
if ( m_pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART_DASH ) ) |
|
{ |
|
flTargetSpeed = tf_halloween_kart_dash_speed.GetFloat(); |
|
flMaxMoveSpeed = tf_halloween_kart_dash_speed.GetFloat(); |
|
flTargetMoveSpeed = flTargetSpeed; |
|
flSmoothMoveSpeed = flTargetSpeed; |
|
flAcceleration = tf_halloween_kart_dash_accel.GetFloat(); |
|
} |
|
|
|
m_pTFPlayer->SetCurrentTauntMoveSpeed( flTargetMoveSpeed ); |
|
float flLeanAccel = flTargetSpeed > flSmoothMoveSpeed ? flAcceleration : flTargetSpeed < flSmoothMoveSpeed ? -flAcceleration : 0.f; |
|
flLeanAccel = Sign( m_pTFPlayer->GetCurrentTauntMoveSpeed() ) != Sign( flTargetSpeed ) ? -flLeanAccel : flLeanAccel; |
|
m_pTFPlayer->m_PlayerAnimState->Vehicle_LeanAccel( flLeanAccel ); |
|
|
|
#ifdef DEBUG |
|
engine->Con_NPrintf( 0, "Speed: %3.2f", m_pTFPlayer->GetCurrentTauntMoveSpeed() ); |
|
engine->Con_NPrintf( 1, "Target: %3.2f", flTargetSpeed ); |
|
engine->Con_NPrintf( 2, "Accell: %3.2f", flAcceleration ); |
|
#endif |
|
|
|
mv->m_flMaxSpeed = flMaxMoveSpeed; |
|
mv->m_flForwardMove = flSmoothMoveSpeed; |
|
mv->m_flClientMaxSpeed = flMaxMoveSpeed; |
|
mv->m_flSideMove = 0.f; // No sideways movement |
|
} |
|
|
|
|
|
bool CTFGameMovement::HighMaxSpeedMove() |
|
{ |
|
if ( fabsf( mv->m_flForwardMove ) < player->MaxSpeed() ) |
|
{ |
|
if ( AlmostEqual( mv->m_flForwardMove, cl_forwardspeed.GetFloat() ) ) |
|
{ |
|
mv->m_flForwardMove = player->MaxSpeed(); |
|
} |
|
else if ( AlmostEqual( mv->m_flForwardMove, -cl_backspeed.GetFloat() ) ) |
|
{ |
|
mv->m_flForwardMove = -player->MaxSpeed(); |
|
} |
|
} |
|
|
|
if ( fabsf( mv->m_flSideMove ) < player->MaxSpeed() ) |
|
{ |
|
if ( AlmostEqual( mv->m_flSideMove, cl_sidespeed.GetFloat() ) ) |
|
{ |
|
mv->m_flSideMove = player->MaxSpeed(); |
|
} |
|
else if ( AlmostEqual( mv->m_flSideMove, -cl_sidespeed.GetFloat() ) ) |
|
{ |
|
mv->m_flSideMove = -player->MaxSpeed(); |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
bool CTFGameMovement::CanAccelerate() |
|
{ |
|
// Only allow the player to accelerate when in certain states. |
|
int nCurrentState = m_pTFPlayer->m_Shared.GetState(); |
|
if ( nCurrentState == TF_STATE_ACTIVE ) |
|
{ |
|
return player->GetWaterJumpTime() == 0; |
|
} |
|
else if ( player->IsObserver() ) |
|
{ |
|
return true; |
|
} |
|
else |
|
{ |
|
return false; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Check to see if we are in water. If so the jump button acts like a |
|
// swim upward key. |
|
//----------------------------------------------------------------------------- |
|
bool CTFGameMovement::CheckWaterJumpButton( void ) |
|
{ |
|
// See if we are water jumping. If so, decrement count and return. |
|
if ( player->m_flWaterJumpTime ) |
|
{ |
|
player->m_flWaterJumpTime -= gpGlobals->frametime; |
|
if (player->m_flWaterJumpTime < 0) |
|
{ |
|
player->m_flWaterJumpTime = 0; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
// In water above our waist. |
|
if ( player->GetWaterLevel() >= 2 || m_pTFPlayer->m_Shared.InCond( TF_COND_SWIMMING_NO_EFFECTS ) ) |
|
{ |
|
// Swimming, not jumping. |
|
SetGroundEntity( NULL ); |
|
|
|
int iCannotSwim = 0; |
|
CALL_ATTRIB_HOOK_INT_ON_OTHER( m_pTFPlayer, iCannotSwim, cannot_swim ); |
|
if ( iCannotSwim ) |
|
{ |
|
return false; |
|
} |
|
|
|
// We move up a certain amount. |
|
if ( player->GetWaterType() == CONTENTS_WATER || m_pTFPlayer->m_Shared.InCond( TF_COND_SWIMMING_NO_EFFECTS ) ) |
|
{ |
|
mv->m_vecVelocity[2] = 100; |
|
} |
|
else if ( player->GetWaterType() == CONTENTS_SLIME ) |
|
{ |
|
mv->m_vecVelocity[2] = 80; |
|
} |
|
|
|
// Play swimming sound. |
|
if ( player->m_flSwimSoundTime <= 0 && !m_pTFPlayer->m_Shared.InCond( TF_COND_SWIMMING_NO_EFFECTS ) ) |
|
{ |
|
// Don't play sound again for 1 second. |
|
player->m_flSwimSoundTime = 1000; |
|
PlaySwimSound(); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
void CTFGameMovement::AirDash( void ) |
|
{ |
|
// Apply approx. the jump velocity added to an air dash. |
|
Assert( GetCurrentGravity() == 800.0f ); |
|
|
|
float flJumpMod = 1.f; |
|
// Passive version |
|
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_pTFPlayer, flJumpMod, mod_jump_height ); |
|
// Weapon-restricted version |
|
CTFWeaponBase *pWpn = m_pTFPlayer->GetActiveTFWeapon(); |
|
if ( pWpn ) |
|
{ |
|
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWpn, flJumpMod, mod_jump_height_from_weapon ); |
|
} |
|
|
|
// Lose hype on airdash |
|
int iHypeResetsOnJump = 0; |
|
CALL_ATTRIB_HOOK_INT_ON_OTHER( m_pTFPlayer, iHypeResetsOnJump, hype_resets_on_jump ); |
|
if ( iHypeResetsOnJump != 0 ) |
|
{ |
|
// Loose x hype on jump |
|
float flHype = m_pTFPlayer->m_Shared.GetScoutHypeMeter(); |
|
m_pTFPlayer->m_Shared.SetScoutHypeMeter( flHype - iHypeResetsOnJump ); |
|
m_pTFPlayer->TeamFortress_SetSpeed(); |
|
} |
|
|
|
if ( m_pTFPlayer->m_Shared.GetCarryingRuneType() == RUNE_AGILITY ) |
|
{ |
|
flJumpMod *= 1.8f; |
|
} |
|
|
|
float flDashZ = 268.3281572999747f * flJumpMod; |
|
|
|
// Get the wish direction. |
|
Vector vecForward, vecRight; |
|
AngleVectors( mv->m_vecViewAngles, &vecForward, &vecRight, NULL ); |
|
vecForward.z = 0.0f; |
|
vecRight.z = 0.0f; |
|
VectorNormalize( vecForward ); |
|
VectorNormalize( vecRight ); |
|
|
|
// Copy movement amounts |
|
float flForwardMove = mv->m_flForwardMove; |
|
float flSideMove = mv->m_flSideMove; |
|
|
|
// Find the direction,velocity in the x,y plane. |
|
Vector vecWishDirection( ( ( vecForward.x * flForwardMove ) + ( vecRight.x * flSideMove ) ), |
|
( ( vecForward.y * flForwardMove ) + ( vecRight.y * flSideMove ) ), |
|
0.0f ); |
|
|
|
// Update the velocity on the scout. |
|
mv->m_vecVelocity = vecWishDirection; |
|
mv->m_vecVelocity.z += flDashZ; |
|
|
|
int iAirDash = m_pTFPlayer->m_Shared.GetAirDash(); |
|
if ( iAirDash == 0 ) |
|
{ |
|
#if defined(GAME_DLL) |
|
// Our first air jump. |
|
m_pTFPlayer->SpeakConceptIfAllowed( MP_CONCEPT_DOUBLE_JUMP, "started_jumping:1" ); |
|
#else |
|
IGameEvent *event = gameeventmanager->CreateEvent( "air_dash" ); |
|
if ( event ) |
|
{ |
|
event->SetInt( "player", m_pTFPlayer->GetUserID() ); |
|
gameeventmanager->FireEventClientSide( event ); |
|
} |
|
#endif |
|
} |
|
else |
|
{ |
|
#ifdef GAME_DLL |
|
// Exertion damage from multi-dashing ( atomizer ) |
|
if ( !m_pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_SPEED_BOOST ) && !m_pTFPlayer->m_Shared.InCond( TF_COND_SODAPOPPER_HYPE ) ) |
|
{ |
|
m_pTFPlayer->TakeDamage( CTakeDamageInfo( m_pTFPlayer, m_pTFPlayer, vec3_origin, m_pTFPlayer->WorldSpaceCenter( ), 10.f, DMG_BULLET ) ); |
|
} |
|
#endif |
|
} |
|
m_pTFPlayer->m_Shared.SetAirDash( iAirDash+1 ); |
|
|
|
// Play the gesture. |
|
m_pTFPlayer->DoAnimationEvent( PLAYERANIMEVENT_DOUBLEJUMP ); |
|
#ifdef GAME_DLL |
|
// Pitch shift a sound for all airdashes greater then 1 |
|
if ( iAirDash > 0 ) |
|
{ |
|
EmitSound_t params; |
|
params.m_pSoundName = "General.banana_slip"; |
|
params.m_flSoundTime = 0; |
|
params.m_pflSoundDuration = 0; |
|
//params.m_bWarnOnDirectWaveReference = true; |
|
CPASFilter filter( m_pTFPlayer->GetAbsOrigin( ) ); |
|
params.m_flVolume = 0.1f; |
|
params.m_SoundLevel = SNDLVL_25dB; |
|
params.m_nPitch = RemapVal( iAirDash, 1.0f, 5.0f, 100.f, 120.f ); |
|
params.m_nFlags |= ( SND_CHANGE_PITCH | SND_CHANGE_VOL ); |
|
m_pTFPlayer->StopSound( "General.banana_slip" ); |
|
m_pTFPlayer->EmitSound( filter, m_pTFPlayer->entindex( ), params ); |
|
} |
|
#endif // GAME_DLL |
|
} |
|
|
|
// Only allow bunny jumping up to 1.2x server / player maxspeed setting |
|
#define BUNNYJUMP_MAX_SPEED_FACTOR 1.2f |
|
|
|
void CTFGameMovement::PreventBunnyJumping() |
|
{ |
|
if ( m_pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) ) |
|
return; |
|
|
|
// Speed at which bunny jumping is limited |
|
float maxscaledspeed = BUNNYJUMP_MAX_SPEED_FACTOR * player->m_flMaxspeed; |
|
if ( maxscaledspeed <= 0.0f ) |
|
return; |
|
|
|
// Current player speed |
|
float spd = mv->m_vecVelocity.Length(); |
|
if ( spd <= maxscaledspeed ) |
|
return; |
|
|
|
// Apply this cropping fraction to velocity |
|
float fraction = ( maxscaledspeed / spd ); |
|
|
|
|
|
mv->m_vecVelocity *= fraction; |
|
} |
|
|
|
void CTFGameMovement::ToggleParachute() |
|
{ |
|
if ( (m_pTFPlayer->GetFlags() & FL_ONGROUND) || (mv->m_nOldButtons & IN_JUMP) ) |
|
return; |
|
|
|
// Can not add if in kart (Kart code does it for spell) but players can manually undeploy |
|
if ( m_pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) ) |
|
{ |
|
if ( m_pTFPlayer->m_Shared.InCond( TF_COND_PARACHUTE_DEPLOYED ) ) |
|
{ |
|
m_pTFPlayer->m_Shared.RemoveCond( TF_COND_PARACHUTE_DEPLOYED ); |
|
} |
|
return; |
|
} |
|
|
|
// Check for Parachute and deploy / undeploy |
|
int iParachute = 0; |
|
// Passive version |
|
CALL_ATTRIB_HOOK_INT_ON_OTHER( m_pTFPlayer, iParachute, parachute_attribute ); |
|
if ( iParachute ) |
|
{ |
|
// Toggle between the conditions |
|
if ( m_pTFPlayer->m_Shared.InCond( TF_COND_PARACHUTE_DEPLOYED ) ) |
|
{ |
|
m_pTFPlayer->m_Shared.RemoveCond( TF_COND_PARACHUTE_DEPLOYED ); |
|
} |
|
else |
|
{ |
|
int iParachuteDisabled = 0; |
|
CALL_ATTRIB_HOOK_INT_ON_OTHER( m_pTFPlayer, iParachuteDisabled, parachute_disabled ); |
|
if ( !iParachuteDisabled ) |
|
{ |
|
m_pTFPlayer->m_Shared.AddCond( TF_COND_PARACHUTE_DEPLOYED ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
bool CTFGameMovement::CheckJumpButton() |
|
{ |
|
// Are we dead? Then we cannot jump. |
|
if ( player->pl.deadflag ) |
|
return false; |
|
|
|
// Check to see if we are in water. |
|
if ( !CheckWaterJumpButton() ) |
|
return false; |
|
|
|
if ( m_pTFPlayer->GetGrapplingHookTarget() ) |
|
{ |
|
float flStartZ = mv->m_vecVelocity[2]; |
|
mv->m_vecVelocity[2] += tf_grapplinghook_jump_up_speed.GetFloat(); |
|
|
|
// Heavy gets a jump height reduction across the board, even if he has Agility |
|
// Powered up flag carriers get the same penalty |
|
if ( m_pTFPlayer->GetPlayerClass()->GetClassIndex() == TF_CLASS_HEAVYWEAPONS || ( m_pTFPlayer->m_Shared.GetCarryingRuneType() != RUNE_NONE && m_pTFPlayer->HasTheFlag() ) ) |
|
{ |
|
mv->m_vecVelocity[2] *= 0.80f; |
|
} |
|
else if ( m_pTFPlayer->m_Shared.GetCarryingRuneType() != RUNE_AGILITY && m_pTFPlayer->m_Shared.GetCarryingRuneType() != RUNE_NONE && m_pTFPlayer->HasTheFlag() ) |
|
{ |
|
mv->m_vecVelocity[2] *= 0.80f; |
|
} |
|
|
|
if ( mv->m_vecVelocity[2] > GetAirSpeedCap() ) |
|
mv->m_vecVelocity[2] = GetAirSpeedCap(); |
|
|
|
// Apply gravity. |
|
FinishGravity(); |
|
|
|
mv->m_outJumpVel.z = mv->m_vecVelocity[2] - flStartZ; |
|
mv->m_outStepHeight += 0.15f; |
|
mv->m_nOldButtons |= IN_JUMP; |
|
|
|
return true; |
|
} |
|
|
|
// holding jump key will make ghost fly |
|
if ( m_pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) ) |
|
{ |
|
float flStartZ = mv->m_vecVelocity[2]; |
|
mv->m_vecVelocity[2] = tf_ghost_up_speed.GetFloat(); |
|
|
|
// Apply gravity. |
|
FinishGravity(); |
|
|
|
mv->m_outJumpVel.z = mv->m_vecVelocity[2] - flStartZ; |
|
mv->m_outStepHeight += 0.15f; |
|
mv->m_nOldButtons |= IN_JUMP; |
|
return true; |
|
} |
|
|
|
// Can't jump if our weapon disallows it. |
|
CTFWeaponBase *pWpn = m_pTFPlayer->GetActiveTFWeapon(); |
|
if ( pWpn && !pWpn->OwnerCanJump() ) |
|
return false; |
|
|
|
// Cannot jump while taunting |
|
if ( m_pTFPlayer->m_Shared.InCond( TF_COND_TAUNTING ) ) |
|
return false; |
|
|
|
// Check to see if the player is a scout. |
|
bool bScout = m_pTFPlayer->GetPlayerClass()->IsClass( TF_CLASS_SCOUT ); |
|
bool bAirDash = false; |
|
bool bOnGround = ( player->GetGroundEntity() != NULL ); |
|
|
|
ToggleParachute(); |
|
|
|
// Cannot jump will ducked. |
|
if ( player->GetFlags() & FL_DUCKING ) |
|
{ |
|
// Let a scout do it. |
|
bool bAllow = ( bScout && !bOnGround ); |
|
|
|
if ( !bAllow ) |
|
return false; |
|
} |
|
|
|
// Cannot jump while in the unduck transition. |
|
if ( ( player->m_Local.m_bDucking && ( player->GetFlags() & FL_DUCKING ) ) || ( player->m_Local.m_flDuckJumpTime > 0.0f ) ) |
|
return false; |
|
|
|
// Cannot jump again until the jump button has been released. |
|
if ( mv->m_nOldButtons & IN_JUMP ) |
|
return false; |
|
|
|
// In air, so ignore jumps |
|
// (unless you are a scout or ghost or parachute |
|
if ( !bOnGround ) |
|
{ |
|
if ( m_pTFPlayer->CanAirDash() ) |
|
{ |
|
bAirDash = true; |
|
} |
|
else |
|
{ |
|
mv->m_nOldButtons |= IN_JUMP; |
|
return false; |
|
} |
|
} |
|
|
|
// Check for an air dash. |
|
if ( bAirDash ) |
|
{ |
|
AirDash(); |
|
// Reset air duck for Scouts on AirDash. |
|
m_pTFPlayer->m_Shared.SetAirDucked( 0 ); |
|
return true; |
|
} |
|
|
|
PreventBunnyJumping(); |
|
|
|
// Start jump animation and player sound (specific TF animation and flags). |
|
m_pTFPlayer->DoAnimationEvent( PLAYERANIMEVENT_JUMP ); |
|
player->PlayStepSound( (Vector &)mv->GetAbsOrigin(), player->m_pSurfaceData, 1.0, true ); |
|
m_pTFPlayer->m_Shared.SetJumping( true ); |
|
|
|
if ( m_pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) ) |
|
{ |
|
m_pTFPlayer->EmitSound( "BumperCar.Jump" ); |
|
} |
|
|
|
// Set the player as in the air. |
|
SetGroundEntity( NULL ); |
|
|
|
// Check the surface the player is standing on to see if it impacts jumping. |
|
float flGroundFactor = 1.0f; |
|
if ( player->m_pSurfaceData ) |
|
{ |
|
flGroundFactor = player->m_pSurfaceData->game.jumpFactor; |
|
} |
|
|
|
// fMul = sqrt( 2.0 * gravity * jump_height (21.0units) ) * GroundFactor |
|
Assert( GetCurrentGravity() == 800.0f ); |
|
|
|
float flJumpMod = 1.f; |
|
//if ( m_pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) ) |
|
//{ |
|
// flJumpMod *= 1.3f; |
|
//} |
|
|
|
// Passive version |
|
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_pTFPlayer, flJumpMod, mod_jump_height ); |
|
// Weapon-restricted version |
|
if ( pWpn ) |
|
{ |
|
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( pWpn, flJumpMod, mod_jump_height_from_weapon ); |
|
} |
|
/* |
|
#ifdef STAGING_ONLY |
|
if ( m_pTFPlayer->m_Shared.InCond( TF_COND_SPACE_GRAVITY ) ) |
|
{ |
|
flJumpMod *= tf_space_gravity_jump_multipler.GetFloat(); |
|
} |
|
|
|
#endif // STAGING_ONLY |
|
*/ |
|
if ( m_pTFPlayer->m_Shared.GetCarryingRuneType() == RUNE_AGILITY ) |
|
{ |
|
flJumpMod *= 1.8f; |
|
} |
|
|
|
float flMul = ( 289.0f * flJumpMod ) * flGroundFactor; |
|
|
|
// Save the current z velocity. |
|
float flStartZ = mv->m_vecVelocity[2]; |
|
|
|
// Acclerate upward |
|
if ( ( player->m_Local.m_bDucking ) || ( player->GetFlags() & FL_DUCKING ) ) |
|
{ |
|
// If we are ducking... |
|
// d = 0.5 * g * t^2 - distance traveled with linear accel |
|
// t = sqrt(2.0 * 45 / g) - how long to fall 45 units |
|
// v = g * t - velocity at the end (just invert it to jump up that high) |
|
// v = g * sqrt(2.0 * 45 / g ) |
|
// v^2 = g * g * 2.0 * 45 / g |
|
// v = sqrt( g * 2.0 * 45 ) |
|
mv->m_vecVelocity[2] = flMul; // 2 * gravity * jump_height * ground_factor |
|
} |
|
else |
|
{ |
|
mv->m_vecVelocity[2] += flMul; // 2 * gravity * jump_height * ground_factor |
|
} |
|
|
|
// Apply gravity. |
|
FinishGravity(); |
|
|
|
// Save the output data for the physics system to react to if need be. |
|
mv->m_outJumpVel.z += mv->m_vecVelocity[2] - flStartZ; |
|
mv->m_outStepHeight += 0.15f; |
|
|
|
// Flag that we jumped and don't jump again until it is released. |
|
mv->m_nOldButtons |= IN_JUMP; |
|
return true; |
|
} |
|
|
|
//-------------------------------------------------------- |
|
int CTFGameMovement::CheckStuck( void ) |
|
{ |
|
// assume we are not stuck in a player |
|
m_isPassingThroughEnemies = false; |
|
|
|
if ( tf_resolve_stuck_players.GetBool() ) |
|
{ |
|
const Vector &originalPos = mv->GetAbsOrigin(); |
|
trace_t traceresult; |
|
|
|
TracePlayerBBox( originalPos, originalPos, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, traceresult ); |
|
|
|
#ifdef GAME_DLL |
|
if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && m_pTFPlayer && m_pTFPlayer->GetTeamNumber() == TF_TEAM_PVE_INVADERS ) |
|
{ |
|
if ( traceresult.startsolid ) |
|
{ |
|
if ( m_pTFPlayer->m_playerMovementStuckTimer.HasStarted() && m_pTFPlayer->m_playerMovementStuckTimer.IsElapsed() ) |
|
{ |
|
DevMsg( "%3.2f: A robot is interpenetrating a solid - killed!\n", gpGlobals->curtime ); |
|
|
|
UTIL_LogPrintf( "\"%s<%i><%s><%s>\" startsolid killed (position \"%3.2f %3.2f %3.2f\")\n", |
|
m_pTFPlayer->GetPlayerName(), |
|
m_pTFPlayer->GetUserID(), |
|
m_pTFPlayer->GetNetworkIDString(), |
|
m_pTFPlayer->GetTeam()->GetName(), |
|
m_pTFPlayer->GetAbsOrigin().x, m_pTFPlayer->GetAbsOrigin().y, m_pTFPlayer->GetAbsOrigin().z ); |
|
|
|
m_pTFPlayer->TakeDamage( CTakeDamageInfo( m_pTFPlayer, m_pTFPlayer, vec3_origin, m_pTFPlayer->WorldSpaceCenter(), 999999.9f, DMG_CRUSH ) ); |
|
} |
|
else |
|
{ |
|
if ( traceresult.m_pEnt ) |
|
{ |
|
Warning( "Robot's getting stuck with %s\n", traceresult.m_pEnt->GetClassname() ); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
// Bot is *not* stuck right now. Continually restart timer, so if we become stuck it will count down and expire. |
|
const float stuckTooLongTime = 10.0f; |
|
m_pTFPlayer->m_playerMovementStuckTimer.Start( stuckTooLongTime ); |
|
} |
|
} |
|
#endif |
|
|
|
if ( traceresult.startsolid && traceresult.DidHitNonWorldEntity() ) |
|
{ |
|
if ( traceresult.m_pEnt->IsPlayer() ) |
|
{ |
|
// We are stuck in an enemy player. Don't collide with enemies until we are no longer penetrating them. |
|
m_isPassingThroughEnemies = true; |
|
|
|
// verify position is now clear |
|
TracePlayerBBox( originalPos, originalPos, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, traceresult ); |
|
|
|
if ( !traceresult.DidHit() ) |
|
{ |
|
// no longer stuck |
|
DevMsg( "%3.2f: Resolved stuck player/player\n", gpGlobals->curtime ); |
|
|
|
return 0; |
|
} |
|
} |
|
else if ( fabs( traceresult.m_pEnt->GetAbsVelocity().z ) > 0.7071f && FClassnameIs( traceresult.m_pEnt, "func_tracktrain" ) ) |
|
{ |
|
// we're stuck in a vertically moving tracktrain, assume flat surface normal and move us out |
|
SetGroundEntity( &traceresult ); |
|
|
|
// we're stuck in a vertically moving tracktrain, snap on top of it |
|
const float maxAdjust = 80.0f; |
|
const float step = 10.0f; |
|
Vector tryPos; |
|
for( float shift = step; shift < maxAdjust; shift += step ) |
|
{ |
|
tryPos = mv->GetAbsOrigin(); |
|
tryPos.z += shift; |
|
|
|
TracePlayerBBox( tryPos, tryPos, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, traceresult ); |
|
if ( !traceresult.DidHit() || ( traceresult.m_pEnt && traceresult.m_pEnt->IsPlayer() ) ) |
|
{ |
|
// no longer stuck |
|
mv->SetAbsOrigin( tryPos ); |
|
|
|
DevMsg( "%3.2f: Forced stuck player to top of func_tracktrain\n", gpGlobals->curtime ); |
|
|
|
return 0; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
return BaseClass::CheckStuck(); |
|
} |
|
|
|
|
|
bool CTFGameMovement::CheckWater( void ) |
|
{ |
|
Vector vecPlayerMin = GetPlayerMins(); |
|
Vector vecPlayerMax = GetPlayerMaxs(); |
|
|
|
Vector vecPoint( ( mv->GetAbsOrigin().x + ( vecPlayerMin.x + vecPlayerMax.x ) * 0.5f ), |
|
( mv->GetAbsOrigin().y + ( vecPlayerMin.y + vecPlayerMax.y ) * 0.5f ), |
|
( mv->GetAbsOrigin().z + vecPlayerMin.z + 1 ) ); |
|
|
|
|
|
// Assume that we are not in water at all. |
|
int wl = WL_NotInWater; |
|
int wt = CONTENTS_EMPTY; |
|
|
|
// Check to see if our feet are underwater. |
|
int nContents = GetPointContentsCached( vecPoint, 0 ); |
|
if ( nContents & MASK_WATER ) |
|
{ |
|
// Clear our jump flag, because we have landed in water. |
|
m_pTFPlayer->m_Shared.SetJumping( false ); |
|
|
|
// Set water type and level. |
|
wt = nContents; |
|
wl = WL_Feet; |
|
|
|
float flWaistZ = mv->GetAbsOrigin().z + ( vecPlayerMin.z + vecPlayerMax.z ) * 0.5f + 12.0f; |
|
|
|
// Now check eyes |
|
vecPoint.z = mv->GetAbsOrigin().z + player->GetViewOffset()[2]; |
|
nContents = GetPointContentsCached( vecPoint, 1 ); |
|
if ( nContents & MASK_WATER ) |
|
{ |
|
// In over our eyes |
|
wl = WL_Eyes; |
|
VectorCopy( vecPoint, m_vecWaterPoint ); |
|
m_vecWaterPoint.z = flWaistZ; |
|
} |
|
else |
|
{ |
|
// Now check a point that is at the player hull midpoint (waist) and see if that is underwater. |
|
vecPoint.z = flWaistZ; |
|
nContents = GetPointContentsCached( vecPoint, 2 ); |
|
if ( nContents & MASK_WATER ) |
|
{ |
|
// Set the water level at our waist. |
|
wl = WL_Waist; |
|
VectorCopy( vecPoint, m_vecWaterPoint ); |
|
} |
|
} |
|
} |
|
|
|
// force player to be under water |
|
if ( m_pTFPlayer->m_Shared.InCond( TF_COND_SWIMMING_CURSE ) ) |
|
{ |
|
wl = WL_Eyes; |
|
} |
|
|
|
player->SetWaterLevel( wl ); |
|
player->SetWaterType( wt ); |
|
|
|
// If we just transitioned from not in water to water, record the time for splashes, etc. |
|
if ( ( WL_NotInWater == m_nOldWaterLevel ) && ( wl > WL_NotInWater ) ) |
|
{ |
|
m_flWaterEntryTime = gpGlobals->curtime; |
|
} |
|
#ifdef GAME_DLL |
|
else if ( ( WL_NotInWater == wl ) && ( m_nOldWaterLevel > WL_NotInWater ) ) |
|
{ |
|
m_pTFPlayer->SetWaterExitTime( gpGlobals->curtime ); |
|
} |
|
#endif |
|
|
|
if ( m_nOldWaterLevel != wl ) |
|
{ |
|
m_pTFPlayer->TeamFortress_SetSpeed(); |
|
} |
|
|
|
return ( wl > WL_Feet ); |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFGameMovement::WaterMove( void ) |
|
{ |
|
float wishspeed; |
|
Vector wishdir; |
|
Vector start, dest; |
|
Vector temp; |
|
trace_t pm; |
|
float speed, newspeed, addspeed, accelspeed; |
|
|
|
// Determine movement angles. |
|
Vector vecForward, vecRight, vecUp; |
|
AngleVectors( mv->m_vecViewAngles, &vecForward, &vecRight, &vecUp ); |
|
|
|
// Calculate the desired direction and speed. |
|
Vector vecWishVelocity; |
|
for ( int iAxis = 0 ; iAxis < 3; ++iAxis ) |
|
{ |
|
vecWishVelocity[iAxis] = ( vecForward[iAxis] * mv->m_flForwardMove ) + ( vecRight[iAxis] * mv->m_flSideMove ); |
|
} |
|
|
|
// if you can't swim just sink instead |
|
int iCannotSwim = 0; |
|
CALL_ATTRIB_HOOK_INT_ON_OTHER( m_pTFPlayer, iCannotSwim, cannot_swim ); |
|
if ( iCannotSwim ) |
|
{ |
|
vecWishVelocity[0] *= 0.1; |
|
vecWishVelocity[1] *= 0.1; |
|
vecWishVelocity[2] = -60; |
|
} |
|
// Check for upward velocity (JUMP). |
|
else if ( mv->m_nButtons & IN_JUMP ) |
|
{ |
|
if ( player->GetWaterLevel() == WL_Eyes ) |
|
{ |
|
vecWishVelocity[2] += mv->m_flClientMaxSpeed; |
|
} |
|
} |
|
// Sinking if not moving. |
|
else if ( !mv->m_flForwardMove && !mv->m_flSideMove && !mv->m_flUpMove ) |
|
{ |
|
vecWishVelocity[2] -= 60; |
|
} |
|
// Move up based on view angle. |
|
else |
|
{ |
|
vecWishVelocity[2] += mv->m_flUpMove; |
|
} |
|
|
|
// Copy it over and determine speed |
|
VectorCopy( vecWishVelocity, wishdir ); |
|
wishspeed = VectorNormalize( wishdir ); |
|
|
|
// Cap speed. |
|
if (wishspeed > mv->m_flMaxSpeed) |
|
{ |
|
VectorScale( vecWishVelocity, mv->m_flMaxSpeed/wishspeed, vecWishVelocity ); |
|
wishspeed = mv->m_flMaxSpeed; |
|
} |
|
|
|
// Slow us down a bit. |
|
int iSwimmingMastery = 0; |
|
CALL_ATTRIB_HOOK_INT_ON_OTHER( m_pTFPlayer, iSwimmingMastery, swimming_mastery ); |
|
if ( iSwimmingMastery == 0 ) |
|
{ |
|
wishspeed *= 0.8; |
|
} |
|
|
|
// Water friction |
|
VectorCopy( mv->m_vecVelocity, temp ); |
|
speed = VectorNormalize( temp ); |
|
if ( speed ) |
|
{ |
|
newspeed = speed - gpGlobals->frametime * speed * sv_friction.GetFloat() * player->m_surfaceFriction; |
|
if ( newspeed < 0.1f ) |
|
{ |
|
newspeed = 0; |
|
} |
|
|
|
VectorScale (mv->m_vecVelocity, newspeed/speed, mv->m_vecVelocity); |
|
} |
|
else |
|
{ |
|
newspeed = 0; |
|
} |
|
|
|
// water acceleration |
|
if ( m_pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) ) |
|
{ |
|
VectorNormalize(vecWishVelocity); |
|
accelspeed = sv_accelerate.GetFloat() * wishspeed * gpGlobals->frametime * player->m_surfaceFriction; |
|
for ( int i = 0; i < 3; i++) |
|
{ |
|
float deltaSpeed = accelspeed * vecWishVelocity[i]; |
|
mv->m_vecVelocity[i] += deltaSpeed; |
|
mv->m_outWishVel[i] += deltaSpeed; |
|
} |
|
|
|
float flGhostXYSpeed = mv->m_vecVelocity.Length2D(); |
|
if ( flGhostXYSpeed > tf_ghost_xy_speed.GetFloat() ) |
|
{ |
|
float flGhostXYSpeedScale = tf_ghost_xy_speed.GetFloat() / flGhostXYSpeed; |
|
mv->m_vecVelocity.x *= flGhostXYSpeedScale; |
|
mv->m_vecVelocity.y *= flGhostXYSpeedScale; |
|
} |
|
} |
|
else if (wishspeed >= 0.1f) // old ! |
|
{ |
|
addspeed = wishspeed - newspeed; |
|
if (addspeed > 0) |
|
{ |
|
VectorNormalize(vecWishVelocity); |
|
accelspeed = sv_accelerate.GetFloat() * wishspeed * gpGlobals->frametime * player->m_surfaceFriction; |
|
if (accelspeed > addspeed) |
|
{ |
|
accelspeed = addspeed; |
|
} |
|
|
|
for ( int i = 0; i < 3; i++) |
|
{ |
|
float deltaSpeed = accelspeed * vecWishVelocity[i]; |
|
mv->m_vecVelocity[i] += deltaSpeed; |
|
mv->m_outWishVel[i] += deltaSpeed; |
|
} |
|
} |
|
} |
|
|
|
VectorAdd (mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity); |
|
|
|
// Now move |
|
// assume it is a stair or a slope, so press down from stepheight above |
|
VectorMA (mv->GetAbsOrigin(), gpGlobals->frametime, mv->m_vecVelocity, dest); |
|
|
|
TracePlayerBBox( mv->GetAbsOrigin(), dest, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, pm ); |
|
if ( pm.fraction == 1.0f ) |
|
{ |
|
VectorCopy( dest, start ); |
|
if ( player->m_Local.m_bAllowAutoMovement ) |
|
{ |
|
start[2] += player->m_Local.m_flStepSize + 1; |
|
} |
|
|
|
TracePlayerBBox( start, dest, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, pm ); |
|
|
|
if (!pm.startsolid && !pm.allsolid) |
|
{ |
|
#if 0 |
|
float stepDist = pm.endpos.z - mv->GetAbsOrigin().z; |
|
mv->m_outStepHeight += stepDist; |
|
// walked up the step, so just keep result and exit |
|
|
|
Vector vecNewWaterPoint; |
|
VectorCopy( m_vecWaterPoint, vecNewWaterPoint ); |
|
vecNewWaterPoint.z += ( dest.z - mv->GetAbsOrigin().z ); |
|
bool bOutOfWater = !( enginetrace->GetPointContents( vecNewWaterPoint ) & MASK_WATER ); |
|
if ( bOutOfWater && ( mv->m_vecVelocity.z > 0.0f ) && ( pm.fraction == 1.0f ) ) |
|
{ |
|
// Check the waist level water positions. |
|
trace_t traceWater; |
|
UTIL_TraceLine( vecNewWaterPoint, m_vecWaterPoint, CONTENTS_WATER, player, COLLISION_GROUP_NONE, &traceWater ); |
|
if( traceWater.fraction < 1.0f ) |
|
{ |
|
float flFraction = 1.0f - traceWater.fraction; |
|
|
|
// Vector vecSegment; |
|
// VectorSubtract( mv->GetAbsOrigin(), dest, vecSegment ); |
|
// VectorMA( mv->GetAbsOrigin(), flFraction, vecSegment, mv->GetAbsOrigin() ); |
|
float flZDiff = dest.z - mv->GetAbsOrigin().z; |
|
float flSetZ = mv->GetAbsOrigin().z + ( flFraction * flZDiff ); |
|
flSetZ -= 0.0325f; |
|
|
|
VectorCopy (pm.endpos, mv->GetAbsOrigin()); |
|
mv->GetAbsOrigin().z = flSetZ; |
|
VectorSubtract( mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity ); |
|
mv->m_vecVelocity.z = 0.0f; |
|
} |
|
|
|
} |
|
else |
|
{ |
|
VectorCopy (pm.endpos, mv->GetAbsOrigin()); |
|
VectorSubtract( mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity ); |
|
} |
|
|
|
return; |
|
#endif |
|
float stepDist = pm.endpos.z - mv->GetAbsOrigin().z; |
|
mv->m_outStepHeight += stepDist; |
|
// walked up the step, so just keep result and exit |
|
mv->SetAbsOrigin( pm.endpos ); |
|
VectorSubtract( mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity ); |
|
return; |
|
} |
|
|
|
// Try moving straight along out normal path. |
|
TryPlayerMove(); |
|
} |
|
else |
|
{ |
|
if ( !player->GetGroundEntity() ) |
|
{ |
|
TryPlayerMove(); |
|
VectorSubtract( mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity ); |
|
return; |
|
} |
|
|
|
StepMove( dest, pm ); |
|
} |
|
|
|
VectorSubtract( mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFGameMovement::WalkMove( void ) |
|
{ |
|
// Get the movement angles. |
|
Vector vecForward, vecRight, vecUp; |
|
AngleVectors( mv->m_vecViewAngles, &vecForward, &vecRight, &vecUp ); |
|
vecForward.z = 0.0f; |
|
vecRight.z = 0.0f; |
|
VectorNormalize( vecForward ); |
|
VectorNormalize( vecRight ); |
|
|
|
// Copy movement amounts |
|
float flForwardMove = mv->m_flForwardMove; |
|
float flSideMove = mv->m_flSideMove; |
|
|
|
// Find the direction,velocity in the x,y plane. |
|
Vector vecWishDirection( ( ( vecForward.x * flForwardMove ) + ( vecRight.x * flSideMove ) ), |
|
( ( vecForward.y * flForwardMove ) + ( vecRight.y * flSideMove ) ), |
|
0.0f ); |
|
|
|
// Calculate the speed and direction of movement, then clamp the speed. |
|
float flWishSpeed = VectorNormalize( vecWishDirection ); |
|
flWishSpeed = clamp( flWishSpeed, 0.0f, mv->m_flMaxSpeed ); |
|
|
|
// Accelerate in the x,y plane. |
|
mv->m_vecVelocity.z = 0; |
|
|
|
float flAccelerate = sv_accelerate.GetFloat(); |
|
// if our wish speed is too low (attributes), we must increase acceleration or we'll never overcome friction |
|
// Reverse the basic friction calculation to find our required acceleration |
|
if ( flWishSpeed > 0 && flWishSpeed < CalcWishSpeedThreshold() ) |
|
{ |
|
// accelspeed = accel * gpGlobals->frametime * wishspeed * player->m_surfaceFriction; |
|
// accelspeed > drop; |
|
// drop = accel * frametime * wish * plFriction |
|
// accel > drop / (wish * gametime * plFriction) |
|
// drop = control * (plFriction * sv_friction) * gameTime; |
|
// accel > control * sv_friction / wish |
|
float flSpeed = VectorLength( mv->m_vecVelocity ); |
|
float flControl = (flSpeed < sv_stopspeed.GetFloat()) ? sv_stopspeed.GetFloat() : flSpeed; |
|
flAccelerate = (flControl * sv_friction.GetFloat()) / flWishSpeed + 1; |
|
} |
|
|
|
Accelerate( vecWishDirection, flWishSpeed, flAccelerate ); |
|
Assert( mv->m_vecVelocity.z == 0.0f ); |
|
|
|
// Clamp the players speed in x,y. |
|
float flNewSpeed = VectorLength( mv->m_vecVelocity ); |
|
if ( flNewSpeed > mv->m_flMaxSpeed ) |
|
{ |
|
float flScale = ( mv->m_flMaxSpeed / flNewSpeed ); |
|
mv->m_vecVelocity.x *= flScale; |
|
mv->m_vecVelocity.y *= flScale; |
|
} |
|
|
|
float flForwardPull = m_pTFPlayer->GetMovementForwardPull(); |
|
|
|
if ( flForwardPull > 0.0f ) |
|
{ |
|
mv->m_vecVelocity += vecForward * flForwardPull; |
|
|
|
if ( mv->m_vecVelocity.Length2D() > mv->m_flMaxSpeed ) |
|
{ |
|
VectorNormalize( mv->m_vecVelocity ); |
|
mv->m_vecVelocity *= mv->m_flMaxSpeed; |
|
} |
|
} |
|
|
|
// Now reduce their backwards speed to some percent of max, if they are traveling backwards |
|
// unless they are under some minimum, to not penalize deployed snipers or heavies |
|
if ( tf_clamp_back_speed.GetFloat() < 1.0 && VectorLength( mv->m_vecVelocity ) > tf_clamp_back_speed_min.GetFloat() ) |
|
{ |
|
float flDot = DotProduct( vecForward, mv->m_vecVelocity ); |
|
|
|
// are we moving backwards at all? |
|
if ( flDot < 0 ) |
|
{ |
|
Vector vecBackMove = vecForward * flDot; |
|
Vector vecRightMove = vecRight * DotProduct( vecRight, mv->m_vecVelocity ); |
|
|
|
// clamp the back move vector if it is faster than max |
|
float flBackSpeed = VectorLength( vecBackMove ); |
|
float flMaxBackSpeed = ( mv->m_flMaxSpeed * tf_clamp_back_speed.GetFloat() ); |
|
|
|
if ( flBackSpeed > flMaxBackSpeed ) |
|
{ |
|
vecBackMove *= flMaxBackSpeed / flBackSpeed; |
|
} |
|
|
|
// reassemble velocity |
|
mv->m_vecVelocity = vecBackMove + vecRightMove; |
|
|
|
// Re-run this to prevent crazy values (clients can induce this via usercmd viewangles hacking) |
|
flNewSpeed = VectorLength( mv->m_vecVelocity ); |
|
if ( flNewSpeed > mv->m_flMaxSpeed ) |
|
{ |
|
float flScale = ( mv->m_flMaxSpeed / flNewSpeed ); |
|
mv->m_vecVelocity.x *= flScale; |
|
mv->m_vecVelocity.y *= flScale; |
|
} |
|
} |
|
} |
|
|
|
// Add base velocity to the player's current velocity - base velocity = velocity from conveyors, etc. |
|
VectorAdd( mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity ); |
|
|
|
// Calculate the current speed and return if we are not really moving. |
|
float flSpeed = VectorLength( mv->m_vecVelocity ); |
|
if ( flSpeed < 1.0f ) |
|
{ |
|
// I didn't remove the base velocity here since it wasn't moving us in the first place. |
|
mv->m_vecVelocity.Init(); |
|
return; |
|
} |
|
|
|
// Calculate the destination. |
|
Vector vecDestination; |
|
vecDestination.x = mv->GetAbsOrigin().x + ( mv->m_vecVelocity.x * gpGlobals->frametime ); |
|
vecDestination.y = mv->GetAbsOrigin().y + ( mv->m_vecVelocity.y * gpGlobals->frametime ); |
|
vecDestination.z = mv->GetAbsOrigin().z; |
|
|
|
#ifdef GAME_DLL |
|
// allow bot to approve position change for intentional movement |
|
INextBot *bot = player->MyNextBotPointer(); |
|
if ( bot && bot->GetIntentionInterface()->IsPositionAllowed( bot, vecDestination ) == ANSWER_NO ) |
|
{ |
|
// rejected - stay put |
|
return; |
|
} |
|
#endif |
|
|
|
// Try moving to the destination. |
|
trace_t trace; |
|
TracePlayerBBox( mv->GetAbsOrigin(), vecDestination, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, trace ); |
|
if ( trace.fraction == 1.0f ) |
|
{ |
|
// Made it to the destination (remove the base velocity). |
|
mv->SetAbsOrigin( trace.endpos ); |
|
VectorSubtract( mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity ); |
|
|
|
// Save the wish velocity. |
|
mv->m_outWishVel += ( vecWishDirection * flWishSpeed ); |
|
|
|
// Try and keep the player on the ground. |
|
// NOTE YWB 7/5/07: Don't do this here, our version of CategorizePosition encompasses this test |
|
// StayOnGround(); |
|
|
|
#ifdef CLIENT_DLL |
|
// Track how far we moved (if we're a Scout or an Engineer carrying a building). |
|
CTFPlayer* pTFPlayer = ToTFPlayer( player ); |
|
if ( pTFPlayer->IsPlayerClass( TF_CLASS_SCOUT ) || |
|
( pTFPlayer->IsPlayerClass( TF_CLASS_ENGINEER ) && pTFPlayer->m_Shared.IsCarryingObject() ) ) |
|
{ |
|
float fInchesToMeters = 0.0254f; |
|
float fWorldScale = 0.25; |
|
float fMeters = pTFPlayer->GetMetersRan(); |
|
float fMetersRan = flSpeed*fInchesToMeters*fWorldScale*gpGlobals->frametime; |
|
pTFPlayer->SetMetersRan( fMeters + fMetersRan, gpGlobals->framecount ); |
|
} |
|
#endif |
|
return; |
|
} |
|
|
|
CTFPlayer* pBumpPlayer = ToTFPlayer( trace.m_pEnt ); |
|
if ( pBumpPlayer ) |
|
{ |
|
m_pTFPlayer->m_Shared.EndCharge(); |
|
} |
|
|
|
// Now try and do a step move. |
|
StepMove( vecDestination, trace ); |
|
|
|
// Remove base velocity. |
|
Vector baseVelocity = player->GetBaseVelocity(); |
|
VectorSubtract( mv->m_vecVelocity, baseVelocity, mv->m_vecVelocity ); |
|
|
|
CheckKartWallBumping(); |
|
|
|
// Save the wish velocity. |
|
mv->m_outWishVel += ( vecWishDirection * flWishSpeed ); |
|
|
|
// Try and keep the player on the ground. |
|
// NOTE YWB 7/5/07: Don't do this here, our version of CategorizePosition encompasses this test |
|
// StayOnGround(); |
|
|
|
#if 0 |
|
// Debugging!!! |
|
Vector vecTestVelocity = mv->m_vecVelocity; |
|
vecTestVelocity.z = 0.0f; |
|
float flTestSpeed = VectorLength( vecTestVelocity ); |
|
if ( baseVelocity.IsZero() && ( flTestSpeed > ( mv->m_flMaxSpeed + 1.0f ) ) ) |
|
{ |
|
Msg( "Step Max Speed < %f\n", flTestSpeed ); |
|
} |
|
|
|
if ( tf_showspeed.GetBool() ) |
|
{ |
|
Msg( "Speed=%f\n", flTestSpeed ); |
|
} |
|
|
|
#endif |
|
} |
|
|
|
void CTFGameMovement::CheckKartWallBumping() |
|
{ |
|
// Karts need to drop their velocity when they bump into things |
|
if ( !m_pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) ) |
|
return; |
|
|
|
const float flCurrentSpeed = m_pTFPlayer->GetCurrentTauntMoveSpeed(); |
|
const float flMaxSpeed = mv->m_vecVelocity.Length(); |
|
const float flClampedSpeed = clamp( flCurrentSpeed, -flMaxSpeed, flMaxSpeed ); |
|
|
|
m_pTFPlayer->SetCurrentTauntMoveSpeed( flClampedSpeed ); |
|
// We hit a wall at a good speed |
|
if ( fabs( flCurrentSpeed ) > 100.f && ( flCurrentSpeed - flClampedSpeed > 100.f ) ) |
|
{ |
|
// Play a flinch to show we impacted something |
|
bool bDashing = m_pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART_DASH ); |
|
m_pTFPlayer->DoAnimationEvent( PLAYERANIMEVENT_CUSTOM_GESTURE, bDashing ? ACT_KART_IMPACT_BIG : ACT_KART_IMPACT ); |
|
|
|
Vector vAim = m_pTFPlayer->GetLocalVelocity(); |
|
vAim.z = 0; |
|
vAim.NormalizeInPlace(); |
|
|
|
// Handle hitting skybox (disappear). |
|
trace_t pWallTrace; |
|
UTIL_TraceLine( m_pTFPlayer->GetAbsOrigin(), m_pTFPlayer->GetAbsOrigin() + vAim * 64, MASK_SOLID, m_pTFPlayer, COLLISION_GROUP_DEBRIS, &pWallTrace ); |
|
|
|
// if we collide with a wall that is 90degrees or higher, bump backwards |
|
if ( pWallTrace.fraction < 1.0 && !( pWallTrace.surface.flags & SURF_SKY ) && pWallTrace.m_pEnt && !pWallTrace.m_pEnt->IsPlayer() && pWallTrace.plane.normal.z <= 0 ) |
|
{ |
|
#ifdef GAME_DLL |
|
// Bounce off the wall, deflect in the direction of the normal of the surface that we collided with |
|
Vector vOld = m_pTFPlayer->GetLocalVelocity(); |
|
Vector vNew = ( -2.0f * pWallTrace.plane.normal.Dot( vOld ) * pWallTrace.plane.normal + vOld ); |
|
vNew.NormalizeInPlace(); |
|
m_pTFPlayer->AddHalloweenKartPushEvent( m_pTFPlayer, NULL, NULL, vNew * vOld.Length() / 2.0f, 0 ); |
|
if ( bDashing ) |
|
{ |
|
// Stop moving |
|
m_pTFPlayer->SetAbsVelocity( vec3_origin ); |
|
m_pTFPlayer->SetCurrentTauntMoveSpeed( 0 ); |
|
m_pTFPlayer->m_Shared.RemoveCond( TF_COND_HALLOWEEN_KART_DASH ); |
|
} |
|
|
|
m_pTFPlayer->SetCurrentTauntMoveSpeed( 0.f ); |
|
#endif |
|
|
|
#ifdef CLIENT_DLL |
|
if ( bDashing ) |
|
{ |
|
m_pTFPlayer->EmitSound( "BumperCar.BumpHard" ); |
|
m_pTFPlayer->ParticleProp()->Create( "kart_impact_sparks", PATTACH_ABSORIGIN, NULL, vAim ); |
|
} |
|
else |
|
{ |
|
m_pTFPlayer->EmitSound( "BumperCar.Bump" ); |
|
m_pTFPlayer->ParticleProp()->Create( "kart_impact_sparks", PATTACH_ABSORIGIN, NULL, vAim ); |
|
} |
|
#endif |
|
} |
|
} |
|
} |
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
float CTFGameMovement::GetAirSpeedCap( void ) |
|
{ |
|
if ( m_pTFPlayer->GetGrapplingHookTarget() ) |
|
{ |
|
if ( m_pTFPlayer->m_Shared.GetCarryingRuneType() == RUNE_AGILITY ) |
|
{ |
|
switch ( m_pTFPlayer->GetPlayerClass()->GetClassIndex() ) |
|
{ |
|
case TF_CLASS_SOLDIER: |
|
case TF_CLASS_HEAVYWEAPONS: |
|
return 850.f; |
|
default: |
|
return 950.f; |
|
} |
|
} |
|
|
|
return tf_grapplinghook_move_speed.GetFloat(); |
|
} |
|
else if ( m_pTFPlayer->m_Shared.InCond( TF_COND_SHIELD_CHARGE ) ) |
|
{ |
|
return tf_max_charge_speed.GetFloat(); |
|
} |
|
else |
|
{ |
|
float flCap = BaseClass::GetAirSpeedCap(); |
|
/* |
|
#ifdef STAGING_ONLY |
|
if ( m_pTFPlayer->m_Shared.InCond( TF_COND_SPACE_GRAVITY ) ) |
|
{ |
|
flCap *= tf_space_aircontrol.GetFloat(); |
|
} |
|
#endif |
|
*/ |
|
if ( m_pTFPlayer->m_Shared.InCond( TF_COND_PARACHUTE_DEPLOYED ) ) |
|
{ |
|
flCap *= tf_parachute_aircontrol.GetFloat(); |
|
} |
|
|
|
if ( m_pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) ) |
|
{ |
|
if ( m_pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART_DASH ) ) |
|
{ |
|
return tf_halloween_kart_dash_speed.GetFloat(); |
|
} |
|
flCap *= tf_halloween_kart_aircontrol.GetFloat(); |
|
} |
|
|
|
float flIncreasedAirControl = 1.f; |
|
CALL_ATTRIB_HOOK_FLOAT_ON_OTHER( m_pTFPlayer, flIncreasedAirControl, mod_air_control ); |
|
|
|
return ( flCap * flIncreasedAirControl ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFGameMovement::AirMove( void ) |
|
{ |
|
// check if grappling move should do step move |
|
if ( m_pTFPlayer->GetGrapplingHookTarget() ) |
|
{ |
|
// Try moving to the destination. |
|
Vector vecDestination = mv->GetAbsOrigin() + ( mv->m_vecVelocity * gpGlobals->frametime ); |
|
trace_t trace; |
|
TracePlayerBBox( mv->GetAbsOrigin(), vecDestination, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, trace ); |
|
if ( trace.fraction != 1.f ) |
|
{ |
|
StepMove( vecDestination, trace ); |
|
return; |
|
} |
|
} |
|
|
|
int i; |
|
Vector wishvel; |
|
float fmove, smove; |
|
Vector wishdir; |
|
float wishspeed; |
|
Vector forward, right, up; |
|
|
|
AngleVectors (mv->m_vecViewAngles, &forward, &right, &up); // Determine movement angles |
|
|
|
// Copy movement amounts |
|
fmove = mv->m_flForwardMove; |
|
smove = mv->m_flSideMove; |
|
|
|
// Zero out z components of movement vectors |
|
forward[2] = 0; |
|
right[2] = 0; |
|
VectorNormalize(forward); // Normalize remainder of vectors |
|
VectorNormalize(right); // |
|
|
|
for (i=0 ; i<2 ; i++) // Determine x and y parts of velocity |
|
wishvel[i] = forward[i]*fmove + right[i]*smove; |
|
wishvel[2] = 0; // Zero out z part of velocity |
|
|
|
VectorCopy (wishvel, wishdir); // Determine maginitude of speed of move |
|
wishspeed = VectorNormalize(wishdir); |
|
|
|
// |
|
// clamp to server defined max speed |
|
// |
|
if ( wishspeed != 0 && (wishspeed > mv->m_flMaxSpeed)) |
|
{ |
|
VectorScale (wishvel, mv->m_flMaxSpeed/wishspeed, wishvel); |
|
wishspeed = mv->m_flMaxSpeed; |
|
} |
|
|
|
float flAirAccel = sv_airaccelerate.GetFloat(); |
|
/* |
|
#ifdef STAGING_ONLY |
|
if ( m_pTFPlayer->m_Shared.InCond( TF_COND_SPACE_GRAVITY ) ) |
|
{ |
|
flAirAccel *= tf_space_aircontrol.GetFloat(); |
|
} |
|
#endif |
|
*/ |
|
AirAccelerate( wishdir, wishspeed, flAirAccel ); |
|
|
|
float flForwardPull = m_pTFPlayer->GetMovementForwardPull(); |
|
|
|
if ( flForwardPull > 0.0f ) |
|
{ |
|
mv->m_vecVelocity += forward * flForwardPull; |
|
|
|
if ( mv->m_vecVelocity.Length2D() > mv->m_flMaxSpeed ) |
|
{ |
|
float flZ = mv->m_vecVelocity.z; |
|
mv->m_vecVelocity.z = 0.0f; |
|
VectorNormalize( mv->m_vecVelocity ); |
|
mv->m_vecVelocity *= mv->m_flMaxSpeed; |
|
mv->m_vecVelocity.z = flZ; |
|
} |
|
} |
|
|
|
// Add in any base velocity to the current velocity. |
|
VectorAdd( mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity ); |
|
|
|
int iBlocked = TryPlayerMove(); |
|
|
|
// TryPlayerMove uses '2' to indictate wall colision wtf |
|
if ( iBlocked & 2 ) |
|
{ |
|
CheckKartWallBumping(); |
|
} |
|
|
|
// Now pull the base velocity back out. Base velocity is set if you are on a moving object, like a conveyor (or maybe another monster?) |
|
VectorSubtract( mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity ); |
|
} |
|
|
|
extern void TracePlayerBBoxForGround( const Vector& start, const Vector& end, const Vector& minsSrc, |
|
const Vector& maxsSrc, IHandleEntity *player, unsigned int fMask, |
|
int collisionGroup, trace_t& pm ); |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// This filter checks against buildable objects. |
|
//----------------------------------------------------------------------------- |
|
class CTraceFilterObject : public CTraceFilterSimple |
|
{ |
|
public: |
|
DECLARE_CLASS( CTraceFilterObject, CTraceFilterSimple ); |
|
|
|
CTraceFilterObject( const IHandleEntity *passentity, int collisionGroup ); |
|
virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ); |
|
}; |
|
|
|
CTraceFilterObject::CTraceFilterObject( const IHandleEntity *passentity, int collisionGroup ) : |
|
BaseClass( passentity, collisionGroup ) |
|
{ |
|
|
|
} |
|
|
|
bool CTraceFilterObject::ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) |
|
{ |
|
CBaseEntity *pMe = const_cast< CBaseEntity * >( EntityFromEntityHandle( GetPassEntity() ) ); |
|
CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity ); |
|
|
|
if ( pEntity ) |
|
{ |
|
#ifdef STAGING_ONLY |
|
// Special case stealth clips through all players and objects |
|
CTFPlayer *pTFPlayerMe = ToTFPlayer( pMe ); |
|
if ( pTFPlayerMe && pTFPlayerMe->m_Shared.InCond( TF_COND_STEALTHED_PHASE ) ) |
|
{ |
|
// if we don't want to collide with anything, just remove this if |
|
if ( pEntity->IsBaseObject() || pEntity->IsPlayer() ) |
|
{ |
|
return false; |
|
} |
|
} |
|
|
|
if ( pEntity->IsPlayer() ) |
|
{ |
|
CTFPlayer *pTFPlayerThem = ToTFPlayer( pEntity ); |
|
if ( pTFPlayerThem && pTFPlayerThem->m_Shared.InCond( TF_COND_STEALTHED_PHASE ) ) |
|
return false; |
|
} |
|
#endif // STAGING_ONLY |
|
|
|
if ( pEntity->IsBaseObject() ) |
|
{ |
|
CBaseObject *pObject = assert_cast<CBaseObject *>( pEntity ); |
|
if ( pObject && pObject->GetOwner() == pMe ) |
|
{ |
|
#ifdef GAME_DLL |
|
// engineer-bots should not collide with their buildables to avoid nasty pathing issues |
|
CTFPlayer *pOwner = ToTFPlayer( pMe ); |
|
if ( pOwner->IsBotOfType( TF_BOT_TYPE ) ) |
|
{ |
|
bool bHitObjectType = pObject->GetType() == OBJ_SENTRYGUN || pObject->GetType() == OBJ_DISPENSER; |
|
if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) |
|
{ |
|
bHitObjectType |= pObject->GetType() == OBJ_TELEPORTER; |
|
} |
|
|
|
if ( bHitObjectType ) |
|
{ |
|
// engineer bots not blocked by sentries or dispensers |
|
return false; |
|
} |
|
} |
|
#endif |
|
// my buildings are solid to me |
|
return true; |
|
} |
|
} |
|
#ifdef GAME_DLL |
|
else if ( pEntity->IsPlayer() ) |
|
{ |
|
if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) |
|
{ |
|
CTFBot *bot = ToTFBot( pEntity ); |
|
|
|
if ( bot && ( bot->HasMission( CTFBot::MISSION_DESTROY_SENTRIES ) || bot->HasMission( CTFBot::MISSION_REPROGRAMMED ) ) ) |
|
{ |
|
// Don't collide with sentry busters since they don't collide with us |
|
return false; |
|
} |
|
|
|
CTFBot *meBot = ToTFBot( pMe ); |
|
|
|
if ( meBot && ( meBot->HasMission( CTFBot::MISSION_DESTROY_SENTRIES ) || meBot->HasMission( CTFBot::MISSION_REPROGRAMMED ) ) ) |
|
{ |
|
// Sentry Busters don't collide with enemies (so they can't be body-blocked) |
|
return false; |
|
} |
|
} |
|
} |
|
else if ( pEntity->MyNextBotPointer() && !pEntity->MyNextBotPointer()->GetLocomotionInterface()->ShouldCollideWith( pMe ) ) |
|
{ |
|
return false; |
|
} |
|
#endif |
|
} |
|
|
|
return CTraceFilterSimple::ShouldHitEntity( pHandleEntity, contentsMask ); |
|
} |
|
|
|
CBaseHandle CTFGameMovement::TestPlayerPosition( const Vector& pos, int collisionGroup, trace_t& pm ) |
|
{ |
|
if( tf_solidobjects.GetBool() == false ) |
|
return BaseClass::TestPlayerPosition( pos, collisionGroup, pm ); |
|
|
|
Ray_t ray; |
|
ray.Init( pos, pos, GetPlayerMins(), GetPlayerMaxs() ); |
|
|
|
CTraceFilterObject traceFilter( mv->m_nPlayerHandle.Get(), collisionGroup ); |
|
enginetrace->TraceRay( ray, PlayerSolidMask(), &traceFilter, &pm ); |
|
|
|
if ( (pm.contents & PlayerSolidMask()) && pm.m_pEnt ) |
|
{ |
|
return pm.m_pEnt->GetRefEHandle(); |
|
} |
|
else |
|
{ |
|
return INVALID_EHANDLE_INDEX; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Traces player movement + position |
|
//----------------------------------------------------------------------------- |
|
void CTFGameMovement::TracePlayerBBox( const Vector& start, const Vector& end, unsigned int fMask, int collisionGroup, trace_t& pm ) |
|
{ |
|
if( tf_solidobjects.GetBool() == false ) |
|
return BaseClass::TracePlayerBBox( start, end, fMask, collisionGroup, pm ); |
|
|
|
Ray_t ray; |
|
ray.Init( start, end, GetPlayerMins(), GetPlayerMaxs() ); |
|
|
|
CTraceFilterObject traceFilter( mv->m_nPlayerHandle.Get(), collisionGroup ); |
|
|
|
enginetrace->TraceRay( ray, fMask, &traceFilter, &pm ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &input - |
|
//----------------------------------------------------------------------------- |
|
void CTFGameMovement::CategorizePosition( void ) |
|
{ |
|
// Observer. |
|
if ( player->IsObserver() ) |
|
return; |
|
|
|
// Reset this each time we-recategorize, otherwise we have bogus friction when we jump into water and plunge downward really quickly |
|
player->m_surfaceFriction = 1.0f; |
|
|
|
// Doing this before we move may introduce a potential latency in water detection, but |
|
// doing it after can get us stuck on the bottom in water if the amount we move up |
|
// is less than the 1 pixel 'threshold' we're about to snap to. Also, we'll call |
|
// this several times per frame, so we really need to avoid sticking to the bottom of |
|
// water on each call, and the converse case will correct itself if called twice. |
|
CheckWater(); |
|
|
|
// If standing on a ladder we are not on ground. |
|
if ( player->GetMoveType() == MOVETYPE_LADDER ) |
|
{ |
|
SetGroundEntity( NULL ); |
|
return; |
|
} |
|
|
|
// Check for a jump. |
|
if ( mv->m_vecVelocity.z > 250.0f ) |
|
{ |
|
#if defined(GAME_DLL) |
|
if ( m_pTFPlayer->m_bTakenBlastDamageSinceLastMovement ) |
|
{ |
|
m_pTFPlayer->SetBlastJumpState( TF_PLAYER_ENEMY_BLASTED_ME ); |
|
} |
|
#endif |
|
|
|
SetGroundEntity( NULL ); |
|
return; |
|
} |
|
|
|
// Calculate the start and end position. |
|
Vector vecStartPos = mv->GetAbsOrigin(); |
|
Vector vecEndPos( mv->GetAbsOrigin().x, mv->GetAbsOrigin().y, ( mv->GetAbsOrigin().z - 2.0f ) ); |
|
|
|
// NOTE YWB 7/5/07: Since we're already doing a traceline here, we'll subsume the StayOnGround (stair debouncing) check into the main traceline we do here to see what we're standing on |
|
bool bUnderwater = ( player->GetWaterLevel() >= WL_Eyes ); |
|
bool bMoveToEndPos = false; |
|
if ( player->GetMoveType() == MOVETYPE_WALK && |
|
player->GetGroundEntity() != NULL && !bUnderwater ) |
|
{ |
|
// if walking and still think we're on ground, we'll extend trace down by stepsize so we don't bounce down slopes |
|
vecEndPos.z -= player->GetStepSize(); |
|
bMoveToEndPos = true; |
|
} |
|
|
|
trace_t trace; |
|
TracePlayerBBox( vecStartPos, vecEndPos, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, trace ); |
|
|
|
// Steep plane, not on ground. |
|
if ( trace.plane.normal.z < 0.7f ) |
|
{ |
|
// Test four sub-boxes, to see if any of them would have found shallower slope we could actually stand on. |
|
TracePlayerBBoxForGround( vecStartPos, vecEndPos, GetPlayerMins(), GetPlayerMaxs(), mv->m_nPlayerHandle.Get(), PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, trace ); |
|
|
|
if ( trace.plane.normal[2] < 0.7f ) |
|
{ |
|
// Too steep. |
|
SetGroundEntity( NULL ); |
|
if ( ( mv->m_vecVelocity.z > 0.0f ) && |
|
( player->GetMoveType() != MOVETYPE_NOCLIP ) ) |
|
{ |
|
player->m_surfaceFriction = 0.25f; |
|
} |
|
} |
|
else |
|
{ |
|
SetGroundEntity( &trace ); |
|
} |
|
} |
|
else |
|
{ |
|
// YWB: This logic block essentially lifted from StayOnGround implementation |
|
if ( bMoveToEndPos && |
|
!trace.startsolid && // not sure we need this check as fraction would == 0.0f? |
|
trace.fraction > 0.0f && // must go somewhere |
|
trace.fraction < 1.0f ) // must hit something |
|
{ |
|
float flDelta = fabs( mv->GetAbsOrigin().z - trace.endpos.z ); |
|
// HACK HACK: The real problem is that trace returning that strange value |
|
// we can't network over based on bit precision of networking origins |
|
if ( flDelta > 0.5f * COORD_RESOLUTION ) |
|
{ |
|
Vector org = mv->GetAbsOrigin(); |
|
org.z = trace.endpos.z; |
|
mv->SetAbsOrigin( org ); |
|
} |
|
} |
|
SetGroundEntity( &trace ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFGameMovement::CheckWaterJump( void ) |
|
{ |
|
Vector flatforward; |
|
Vector flatvelocity; |
|
float curspeed; |
|
|
|
// Jump button down? |
|
bool bJump = ( ( mv->m_nButtons & IN_JUMP ) != 0 ); |
|
|
|
Vector forward, right; |
|
AngleVectors( mv->m_vecViewAngles, &forward, &right, NULL ); // Determine movement angles |
|
|
|
// Already water jumping. |
|
if (player->m_flWaterJumpTime) |
|
return; |
|
|
|
// Don't hop out if we just jumped in |
|
if (mv->m_vecVelocity[2] < -180) |
|
return; // only hop out if we are moving up |
|
|
|
// See if we are backing up |
|
flatvelocity[0] = mv->m_vecVelocity[0]; |
|
flatvelocity[1] = mv->m_vecVelocity[1]; |
|
flatvelocity[2] = 0; |
|
|
|
// Must be moving |
|
curspeed = VectorNormalize( flatvelocity ); |
|
|
|
#if 1 |
|
// Copy movement amounts |
|
float fmove = mv->m_flForwardMove; |
|
float smove = mv->m_flSideMove; |
|
|
|
for ( int iAxis = 0; iAxis < 2; ++iAxis ) |
|
{ |
|
flatforward[iAxis] = forward[iAxis] * fmove + right[iAxis] * smove; |
|
} |
|
#else |
|
// see if near an edge |
|
flatforward[0] = forward[0]; |
|
flatforward[1] = forward[1]; |
|
#endif |
|
flatforward[2] = 0; |
|
VectorNormalize( flatforward ); |
|
|
|
// Are we backing into water from steps or something? If so, don't pop forward |
|
if ( curspeed != 0.0 && ( DotProduct( flatvelocity, flatforward ) < 0.0 ) && !bJump ) |
|
return; |
|
|
|
Vector vecStart; |
|
// Start line trace at waist height (using the center of the player for this here) |
|
vecStart = mv->GetAbsOrigin() + (GetPlayerMins() + GetPlayerMaxs() ) * 0.5; |
|
|
|
Vector vecEnd; |
|
VectorMA( vecStart, TF_WATERJUMP_FORWARD/*tf_waterjump_forward.GetFloat()*/, flatforward, vecEnd ); |
|
|
|
trace_t tr; |
|
TracePlayerBBox( vecStart, vecEnd, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, tr ); |
|
if ( tr.fraction < 1.0 ) // solid at waist |
|
{ |
|
IPhysicsObject *pPhysObj = tr.m_pEnt->VPhysicsGetObject(); |
|
if ( pPhysObj ) |
|
{ |
|
if ( pPhysObj->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) |
|
return; |
|
} |
|
|
|
vecStart.z = mv->GetAbsOrigin().z + player->GetViewOffset().z + WATERJUMP_HEIGHT; |
|
VectorMA( vecStart, TF_WATERJUMP_FORWARD/*tf_waterjump_forward.GetFloat()*/, flatforward, vecEnd ); |
|
VectorMA( vec3_origin, -50.0f, tr.plane.normal, player->m_vecWaterJumpVel ); |
|
|
|
TracePlayerBBox( vecStart, vecEnd, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, tr ); |
|
if ( tr.fraction == 1.0 ) // open at eye level |
|
{ |
|
// Now trace down to see if we would actually land on a standable surface. |
|
VectorCopy( vecEnd, vecStart ); |
|
vecEnd.z -= 1024.0f; |
|
TracePlayerBBox( vecStart, vecEnd, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, tr ); |
|
if ( ( tr.fraction < 1.0f ) && ( tr.plane.normal.z >= 0.7 ) ) |
|
{ |
|
mv->m_vecVelocity[2] = TF_WATERJUMP_UP/*tf_waterjump_up.GetFloat()*/; // Push up |
|
mv->m_nOldButtons |= IN_JUMP; // Don't jump again until released |
|
player->AddFlag( FL_WATERJUMP ); |
|
player->m_flWaterJumpTime = 2000.0f; // Do this for 2 seconds |
|
} |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFGameMovement::CheckFalling( void ) |
|
{ |
|
// if we landed on the ground |
|
if ( player->GetGroundEntity() != NULL && !IsDead() ) |
|
{ |
|
// turn off the jumping flag if we're on ground after a jump |
|
if ( m_pTFPlayer->m_Shared.IsJumping() ) |
|
{ |
|
m_pTFPlayer->m_Shared.SetJumping( false ); |
|
|
|
#ifdef CLIENT_DLL |
|
IGameEvent *event = gameeventmanager->CreateEvent( "landed" ); |
|
if ( event && m_pTFPlayer->IsLocalPlayer() ) |
|
{ |
|
event->SetInt( "player", m_pTFPlayer->GetUserID() ); |
|
gameeventmanager->FireEventClientSide( event ); |
|
} |
|
#endif // CLIENT_DLL |
|
} |
|
} |
|
|
|
BaseClass::CheckFalling(); |
|
} |
|
|
|
void CTFGameMovement::FullWalkMoveUnderwater() |
|
{ |
|
if ( player->GetWaterLevel() == WL_Waist ) |
|
{ |
|
CheckWaterJump(); |
|
} |
|
|
|
// If we are falling again, then we must not trying to jump out of water any more. |
|
if ( ( mv->m_vecVelocity.z < 0.0f ) && player->m_flWaterJumpTime ) |
|
{ |
|
player->m_flWaterJumpTime = 0.0f; |
|
} |
|
|
|
// Was jump button pressed? |
|
if ( mv->m_nButtons & IN_JUMP ) |
|
{ |
|
CheckJumpButton(); |
|
} |
|
else |
|
{ |
|
mv->m_nOldButtons &= ~IN_JUMP; |
|
} |
|
|
|
// Perform regular water movement |
|
WaterMove(); |
|
|
|
// Redetermine position vars |
|
CategorizePosition(); |
|
|
|
// If we are on ground, no downward velocity. |
|
if ( player->GetGroundEntity() != NULL ) |
|
{ |
|
mv->m_vecVelocity[2] = 0; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFGameMovement::FullWalkMove() |
|
{ |
|
if ( !InWater() ) |
|
{ |
|
if ( m_pTFPlayer->m_Shared.InCond( TF_COND_PARACHUTE_DEPLOYED ) && mv->m_vecVelocity[2] < 0 ) |
|
{ |
|
mv->m_vecVelocity[2] = Max( mv->m_vecVelocity[2], tf_parachute_maxspeed_z.GetFloat() ); |
|
|
|
float flDrag = tf_parachute_maxspeed_xy.GetFloat(); |
|
// Instead of clamping, we'll dampen |
|
float flSpeedX = abs( mv->m_vecVelocity[0] ); |
|
float flSpeedY = abs( mv->m_vecVelocity[1] ); |
|
float flReductionX = flSpeedX > flDrag ? ( flSpeedX - flDrag ) / 3.0f - 10.0f : 0; |
|
float flReductionY = flSpeedY > flDrag ? ( flSpeedY - flDrag ) / 3.0f - 10.0f : 0; |
|
|
|
mv->m_vecVelocity[0] = Clamp( mv->m_vecVelocity[0], -flDrag - flReductionX, flDrag + flReductionX ); |
|
mv->m_vecVelocity[1] = Clamp( mv->m_vecVelocity[1], -flDrag - flReductionY, flDrag + flReductionY ); |
|
} |
|
|
|
StartGravity(); |
|
} |
|
|
|
// If we are leaping out of the water, just update the counters. |
|
if ( player->m_flWaterJumpTime ) |
|
{ |
|
// Try to jump out of the water (and check to see if we still are). |
|
WaterJump(); |
|
TryPlayerMove(); |
|
CheckWater(); |
|
return; |
|
} |
|
|
|
// If we are swimming in the water, see if we are nudging against a place we can jump up out |
|
// of, and, if so, start out jump. Otherwise, if we are not moving up, then reset jump timer to 0. |
|
// Also run the swim code if we're a ghost or have the TF_COND_SWIMMING_NO_EFFECTS condition |
|
if ( InWater() || ( m_pTFPlayer && ( m_pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) || m_pTFPlayer->m_Shared.InCond( TF_COND_SWIMMING_NO_EFFECTS ) ) ) ) |
|
{ |
|
FullWalkMoveUnderwater(); |
|
return; |
|
} |
|
|
|
if (mv->m_nButtons & IN_JUMP) |
|
{ |
|
CheckJumpButton(); |
|
} |
|
else |
|
{ |
|
mv->m_nOldButtons &= ~IN_JUMP; |
|
} |
|
|
|
// Make sure velocity is valid. |
|
CheckVelocity(); |
|
|
|
if (player->GetGroundEntity() != NULL) |
|
{ |
|
mv->m_vecVelocity[2] = 0.0; |
|
Friction(); |
|
WalkMove(); |
|
} |
|
else |
|
{ |
|
AirMove(); |
|
} |
|
|
|
// Set final flags. |
|
CategorizePosition(); |
|
|
|
// Add any remaining gravitational component if we are not in water. |
|
if ( !InWater() ) |
|
{ |
|
FinishGravity(); |
|
} |
|
|
|
// If we are on ground, no downward velocity. |
|
if ( player->GetGroundEntity() != NULL ) |
|
{ |
|
mv->m_vecVelocity[2] = 0; |
|
} |
|
|
|
// Handling falling. |
|
CheckFalling(); |
|
|
|
// Make sure velocity is valid. |
|
CheckVelocity(); |
|
|
|
// #ifdef GAME_DLL |
|
// if ( m_pTFPlayer->IsPlayerClass( TF_CLASS_SCOUT ) ) |
|
// { |
|
// CTFWeaponBase* pWeapon = m_pTFPlayer->GetActiveTFWeapon(); |
|
// if ( pWeapon && pWeapon->GetWeaponID() == TF_WEAPON_SODA_POPPER ) |
|
// { |
|
// float speed = VectorLength( mv->m_vecVelocity ); |
|
// float fDist = speed*gpGlobals->frametime; |
|
// float fHype = m_pTFPlayer->m_Shared.GetScoutHypeMeter() + (fDist / tf_scout_hype_mod.GetFloat()); |
|
// if ( fHype > 100.f ) |
|
// fHype = 100.f; |
|
// m_pTFPlayer->m_Shared.SetScoutHypeMeter( fHype ); |
|
// } |
|
// } |
|
// #endif |
|
|
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFGameMovement::FullTossMove( void ) |
|
{ |
|
trace_t pm; |
|
Vector move; |
|
|
|
// add velocity if player is moving |
|
if ( (mv->m_flForwardMove != 0.0f) || (mv->m_flSideMove != 0.0f) || (mv->m_flUpMove != 0.0f)) |
|
{ |
|
Vector forward, right, up; |
|
float fmove, smove; |
|
Vector wishdir, wishvel; |
|
float wishspeed; |
|
int i; |
|
|
|
AngleVectors (mv->m_vecViewAngles, &forward, &right, &up); // Determine movement angles |
|
|
|
// Copy movement amounts |
|
fmove = mv->m_flForwardMove; |
|
smove = mv->m_flSideMove; |
|
|
|
VectorNormalize (forward); // Normalize remainder of vectors. |
|
VectorNormalize (right); // |
|
|
|
for (i=0 ; i<3 ; i++) // Determine x and y parts of velocity |
|
wishvel[i] = forward[i]*fmove + right[i]*smove; |
|
|
|
wishvel[2] += mv->m_flUpMove; |
|
|
|
VectorCopy (wishvel, wishdir); // Determine maginitude of speed of move |
|
wishspeed = VectorNormalize(wishdir); |
|
|
|
// |
|
// Clamp to server defined max speed |
|
// |
|
if (wishspeed > mv->m_flMaxSpeed) |
|
{ |
|
VectorScale (wishvel, mv->m_flMaxSpeed/wishspeed, wishvel); |
|
wishspeed = mv->m_flMaxSpeed; |
|
} |
|
|
|
// Set pmove velocity |
|
Accelerate ( wishdir, wishspeed, sv_accelerate.GetFloat() ); |
|
} |
|
|
|
if ( mv->m_vecVelocity[2] > 0 ) |
|
{ |
|
SetGroundEntity( NULL ); |
|
} |
|
|
|
// If on ground and not moving, return. |
|
if ( player->GetGroundEntity() != NULL ) |
|
{ |
|
if (VectorCompare(player->GetBaseVelocity(), vec3_origin) && |
|
VectorCompare(mv->m_vecVelocity, vec3_origin)) |
|
return; |
|
} |
|
|
|
CheckVelocity(); |
|
|
|
// add gravity |
|
if ( player->GetMoveType() == MOVETYPE_FLYGRAVITY ) |
|
{ |
|
AddGravity(); |
|
} |
|
|
|
// move origin |
|
// Base velocity is not properly accounted for since this entity will move again after the bounce without |
|
// taking it into account |
|
VectorAdd (mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity); |
|
|
|
CheckVelocity(); |
|
|
|
VectorScale (mv->m_vecVelocity, gpGlobals->frametime, move); |
|
VectorSubtract (mv->m_vecVelocity, player->GetBaseVelocity(), mv->m_vecVelocity); |
|
|
|
PushEntity( move, &pm ); // Should this clear basevelocity |
|
|
|
CheckVelocity(); |
|
|
|
if (pm.allsolid) |
|
{ |
|
// entity is trapped in another solid |
|
SetGroundEntity( &pm ); |
|
mv->m_vecVelocity.Init(); |
|
return; |
|
} |
|
|
|
if ( pm.fraction != 1.0f ) |
|
{ |
|
PerformFlyCollisionResolution( pm, move ); |
|
} |
|
|
|
// Check for in water |
|
CheckWater(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Does the basic move attempting to climb up step heights. It uses |
|
// the mv->GetAbsOrigin() and mv->m_vecVelocity. It returns a new |
|
// new mv->GetAbsOrigin(), mv->m_vecVelocity, and mv->m_outStepHeight. |
|
//----------------------------------------------------------------------------- |
|
void CTFGameMovement::StepMove( Vector &vecDestination, trace_t &trace ) |
|
{ |
|
trace_t saveTrace; |
|
saveTrace = trace; |
|
|
|
Vector vecEndPos; |
|
VectorCopy( vecDestination, vecEndPos ); |
|
|
|
Vector vecPos, vecVel; |
|
VectorCopy( mv->GetAbsOrigin(), vecPos ); |
|
VectorCopy( mv->m_vecVelocity, vecVel ); |
|
|
|
bool bLowRoad = false; |
|
bool bUpRoad = true; |
|
|
|
// First try the "high road" where we move up and over obstacles |
|
if ( player->m_Local.m_bAllowAutoMovement ) |
|
{ |
|
// Trace up by step height |
|
VectorCopy( mv->GetAbsOrigin(), vecEndPos ); |
|
vecEndPos.z += player->m_Local.m_flStepSize + DIST_EPSILON; |
|
TracePlayerBBox( mv->GetAbsOrigin(), vecEndPos, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, trace ); |
|
if ( !trace.startsolid && !trace.allsolid ) |
|
{ |
|
mv->SetAbsOrigin( trace.endpos ); |
|
} |
|
|
|
// Trace over from there |
|
TryPlayerMove(); |
|
|
|
// Then trace back down by step height to get final position |
|
VectorCopy( mv->GetAbsOrigin(), vecEndPos ); |
|
vecEndPos.z -= player->m_Local.m_flStepSize + DIST_EPSILON; |
|
TracePlayerBBox( mv->GetAbsOrigin(), vecEndPos, PlayerSolidMask(), COLLISION_GROUP_PLAYER_MOVEMENT, trace ); |
|
// If the trace ended up in empty space, copy the end over to the origin. |
|
if ( !trace.startsolid && !trace.allsolid ) |
|
{ |
|
mv->SetAbsOrigin( trace.endpos ); |
|
} |
|
|
|
// If we are not on the standable ground any more or going the "high road" didn't move us at all, then we'll also want to check the "low road" |
|
if ( ( trace.fraction != 1.0f && |
|
trace.plane.normal[2] < 0.7 ) || VectorCompare( mv->GetAbsOrigin(), vecPos ) ) |
|
{ |
|
bLowRoad = true; |
|
bUpRoad = false; |
|
} |
|
} |
|
else |
|
{ |
|
bLowRoad = true; |
|
bUpRoad = false; |
|
} |
|
|
|
if ( bLowRoad ) |
|
{ |
|
// Save off upward results |
|
Vector vecUpPos, vecUpVel; |
|
if ( bUpRoad ) |
|
{ |
|
VectorCopy( mv->GetAbsOrigin(), vecUpPos ); |
|
VectorCopy( mv->m_vecVelocity, vecUpVel ); |
|
} |
|
|
|
// Take the "low" road |
|
mv->SetAbsOrigin( vecPos ); |
|
VectorCopy( vecVel, mv->m_vecVelocity ); |
|
VectorCopy( vecDestination, vecEndPos ); |
|
TryPlayerMove( &vecEndPos, &saveTrace ); |
|
|
|
// Down results. |
|
Vector vecDownPos, vecDownVel; |
|
VectorCopy( mv->GetAbsOrigin(), vecDownPos ); |
|
VectorCopy( mv->m_vecVelocity, vecDownVel ); |
|
|
|
if ( bUpRoad ) |
|
{ |
|
float flUpDist = ( vecUpPos.x - vecPos.x ) * ( vecUpPos.x - vecPos.x ) + ( vecUpPos.y - vecPos.y ) * ( vecUpPos.y - vecPos.y ); |
|
float flDownDist = ( vecDownPos.x - vecPos.x ) * ( vecDownPos.x - vecPos.x ) + ( vecDownPos.y - vecPos.y ) * ( vecDownPos.y - vecPos.y ); |
|
|
|
// decide which one went farther |
|
if ( flUpDist >= flDownDist ) |
|
{ |
|
mv->SetAbsOrigin( vecUpPos ); |
|
VectorCopy( vecUpVel, mv->m_vecVelocity ); |
|
|
|
// copy z value from the Low Road move |
|
mv->m_vecVelocity.z = vecDownVel.z; |
|
} |
|
} |
|
} |
|
|
|
float flStepDist = mv->GetAbsOrigin().z - vecPos.z; |
|
if ( flStepDist > 0 ) |
|
{ |
|
mv->m_outStepHeight += flStepDist; |
|
} |
|
} |
|
|
|
bool CTFGameMovement::GameHasLadders() const |
|
{ |
|
return false; |
|
} |
|
|
|
void CTFGameMovement::SetGroundEntity( trace_t *pm ) |
|
{ |
|
if ( m_pTFPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_KART ) && !m_pTFPlayer->GetGroundEntity() && pm && pm->m_pEnt ) |
|
{ |
|
m_pTFPlayer->EmitSound( "BumperCar.JumpLand" ); |
|
} |
|
|
|
BaseClass::SetGroundEntity( pm ); |
|
if ( pm && pm->m_pEnt ) |
|
{ |
|
#ifdef GAME_DLL |
|
int iAirDash = m_pTFPlayer->m_Shared.GetAirDash(); |
|
if ( iAirDash > 0 ) |
|
{ |
|
m_pTFPlayer->SpeakConceptIfAllowed( MP_CONCEPT_DOUBLE_JUMP, "started_jumping:0" ); |
|
} |
|
m_pTFPlayer->m_Shared.SetWeaponKnockbackID( -1 ); |
|
m_pTFPlayer->m_bScattergunJump = false; |
|
#endif // GAME_DLL |
|
m_pTFPlayer->m_Shared.SetAirDash( 0 ); |
|
m_pTFPlayer->m_Shared.SetAirDucked( 0 ); |
|
|
|
if ( m_pTFPlayer->m_Shared.InCond( TF_COND_GRAPPLINGHOOK_SAFEFALL ) ) |
|
{ |
|
// CheckFalling happens after this. reset the fall velocity to prevent fall damage |
|
if ( tf_grapplinghook_prevent_fall_damage.GetBool() ) |
|
player->m_Local.m_flFallVelocity = 0; |
|
|
|
m_pTFPlayer->m_Shared.RemoveCond( TF_COND_GRAPPLINGHOOK_SAFEFALL ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFGameMovement::PlayerRoughLandingEffects( float fvol ) |
|
{ |
|
if ( m_pTFPlayer ) |
|
{ |
|
/* |
|
#ifdef STAGING_ONLY |
|
// No impact effects if we're in space low-grav |
|
if ( m_pTFPlayer->m_Shared.InCond( TF_COND_SPACE_GRAVITY ) ) |
|
{ |
|
return; |
|
} |
|
#endif // STAGING_ONLY |
|
*/ |
|
// don't play landing sound when grappling hook into a surface |
|
if ( m_pTFPlayer->m_Shared.InCond( TF_COND_GRAPPLINGHOOK ) ) |
|
{ |
|
return; |
|
} |
|
|
|
if ( m_pTFPlayer->IsPlayerClass(TF_CLASS_SCOUT) ) |
|
{ |
|
// Scouts don't play rumble unless they take damage. |
|
if ( fvol < 1.0 ) |
|
{ |
|
fvol = 0; |
|
} |
|
} |
|
} |
|
|
|
BaseClass::PlayerRoughLandingEffects( fvol ); |
|
} |
|
|
|
#if 0 |
|
// Not being used currently - part of TestDuck! |
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFGameMovement::HandleDuck( int nButtonsPressed ) |
|
{ |
|
// XBOX SERVER ONLY |
|
#if !defined(CLIENT_DLL) |
|
if ( IsX360() && nButtonsPressed & IN_DUCK ) |
|
{ |
|
// Hinting logic |
|
if ( player->GetToggledDuckState() && player->m_nNumCrouches < NUM_CROUCH_HINTS ) |
|
{ |
|
UTIL_HudHintText( player, "#Valve_Hint_Crouch" ); |
|
player->m_nNumCrouches++; |
|
} |
|
} |
|
#endif |
|
|
|
bool bInAir = ( player->GetGroundEntity() == NULL ); |
|
bool bInDuck = ( player->GetFlags() & FL_DUCKING ) ? true : false; |
|
|
|
// Starting a duck. |
|
if ( ( nButtonsPressed & IN_DUCK ) && !bInDuck ) |
|
{ |
|
if ( !player->m_Local.m_bDucking ) |
|
{ |
|
player->m_Local.m_flDucktime = TIME_TO_DUCK_MS; |
|
player->m_Local.m_bDucking = true; |
|
} |
|
else |
|
{ |
|
// Find unduck percentage and calcluate the duck time. |
|
float flPercentage = player->m_Local.m_flDucktime / TIME_TO_UNDUCK_MS; |
|
player->m_Local.m_flDucktime = TIME_TO_DUCK_MS * ( 1.0f - flPercentage ); |
|
} |
|
|
|
if ( m_pTFPlayer->m_Shared.GetAirDash() > 0 ) |
|
{ |
|
m_pTFPlayer->DoAnimationEvent( PLAYERANIMEVENT_DOUBLEJUMP_CROUCH ); |
|
} |
|
} |
|
|
|
// Handle the ducking. |
|
if ( player->m_Local.m_bDucking ) |
|
{ |
|
// Finish in duck transition when transition time is over, in "duck", in air. |
|
if ( ( player->m_Local.m_flDucktime <= 0.0f ) || bInDuck || bInAir ) |
|
{ |
|
FinishDuck(); |
|
} |
|
else |
|
{ |
|
// Calculate the eye offset. |
|
float flDuckFraction = SimpleSpline( 1.0f - ( player->m_Local.m_flDucktime / TIME_TO_DUCK_MS ) ); |
|
SetDuckedEyeOffset( flDuckFraction ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFGameMovement::HandleUnDuck( int nButtonsReleased ) |
|
{ |
|
if ( !player->m_Local.m_bAllowAutoMovement ) |
|
return; |
|
|
|
bool bInAir = ( player->GetGroundEntity() == NULL ); |
|
bool bInDuck = ( player->GetFlags() & FL_DUCKING ) ? true : false; |
|
|
|
// Ending a duck (or trying to). |
|
if ( nButtonsReleased & IN_DUCK ) |
|
{ |
|
if ( bInDuck ) |
|
{ |
|
player->m_Local.m_flDucktime = TIME_TO_UNDUCK_MS; |
|
player->m_Local.m_bDucking = true; |
|
} |
|
else if ( player->m_Local.m_bDucking ) |
|
{ |
|
// Find unduck percentage and calcluate the duck time. |
|
float flPercentage = player->m_Local.m_flDucktime / TIME_TO_DUCK_MS; |
|
player->m_Local.m_flDucktime = TIME_TO_UNDUCK_MS * ( 1.0f - flPercentage ); |
|
} |
|
} |
|
|
|
// Check to see if we are capable of unducking given our environment. |
|
if ( CanUnduck() ) |
|
{ |
|
if ( ( player->m_Local.m_bDucking || player->m_Local.m_bDucked ) ) |
|
{ |
|
// We are unducking now. |
|
player->m_Local.m_bDucking = true; |
|
|
|
// Finish ducking immediately if duck time is over or we are in the air. |
|
if ( player->m_Local.m_flDucktime <= 0.0f || bInAir ) |
|
{ |
|
FinishUnDuck(); |
|
} |
|
else |
|
{ |
|
// Calculate the eye offset. |
|
float flDuckFraction = SimpleSpline( ( player->m_Local.m_flDucktime / TIME_TO_UNDUCK_MS ) ); |
|
SetDuckedEyeOffset( flDuckFraction ); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
// Under something where we cannot unduck - rest. |
|
if ( player->m_Local.m_flDucktime != TIME_TO_UNDUCK_MS ) |
|
{ |
|
player->m_Local.m_flDucktime = TIME_TO_UNDUCK_MS; |
|
player->m_Local.m_bDucked = true; |
|
player->m_Local.m_bDucking = false; |
|
player->AddFlag( FL_DUCKING ); |
|
|
|
// Reset the eye offset. |
|
SetDuckedEyeOffset( 1.0f ); |
|
} |
|
} |
|
} |
|
|
|
void CTFGameMovement::TestDuck( ) |
|
{ |
|
|
|
// Handle buttons. |
|
int nButtonsChanged = ( mv->m_nOldButtons ^ mv->m_nButtons ); |
|
int nButtonsPressed = nButtonsChanged & mv->m_nButtons; |
|
int nButtonsReleased = nButtonsChanged & mv->m_nOldButtons; |
|
if ( mv->m_nButtons & IN_DUCK ) |
|
{ |
|
mv->m_nOldButtons |= IN_DUCK; |
|
} |
|
else |
|
{ |
|
mv->m_nOldButtons &= ~IN_DUCK; |
|
} |
|
|
|
// Handle death. |
|
if ( IsDead() ) |
|
return; |
|
// Slow down ducked players. |
|
HandleDuckingSpeedCrop(); |
|
|
|
// In some ducked state - button press to duck, duck transitions, or fully ducked. |
|
bool bInDuck = ( player->GetFlags() & FL_DUCKING ) ? true : false; |
|
if ( ( mv->m_nButtons & IN_DUCK ) || player->m_Local.m_bDucking || bInDuck ) |
|
{ |
|
// Duck State |
|
if ( ( mv->m_nButtons & IN_DUCK ) ) |
|
{ |
|
HandleDuck( nButtonsPressed ); |
|
} |
|
// Unduck State. |
|
else |
|
{ |
|
HandleUnDuck( nButtonsReleased ); |
|
} |
|
} |
|
} |
|
#endif |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFGameMovement::DuckOverrides() |
|
{ |
|
bool bOnGround = ( player->GetGroundEntity() != NULL ); |
|
|
|
// Don't allowing ducking in water. |
|
if ( ( ( player->GetWaterLevel() >= WL_Feet ) && !bOnGround ) || |
|
player->GetWaterLevel() >= WL_Eyes ) |
|
{ |
|
mv->m_nButtons &= ~IN_DUCK; |
|
} |
|
|
|
if ( !tf_clamp_airducks.GetBool() ) |
|
return; |
|
|
|
// Check the duck timer and disable the duck button. |
|
if ( gpGlobals->curtime < m_pTFPlayer->m_Shared.GetDuckTimer() && bOnGround ) |
|
{ |
|
mv->m_nButtons &= ~IN_DUCK; |
|
} |
|
|
|
// If we're trying to stand up, don't let the player try to re-duck. This |
|
// prevents what the community calls the "Quantum Crouch". The above ducktimer |
|
// covers most of the cases where users play nice and duck and unduck while standing. |
|
// The "Quantum Crouch" occurs when users do the following: |
|
// 0: Get a Dispenser or other waist-high platform in front of you |
|
// 1: Press Jump + Crouch and move towards the platform |
|
// 2: Release Crouch while jumping |
|
// ( this causes the duck timer to start counting down ) |
|
// 3: Land on the platform |
|
// 4: While starting to stand up, press Crouch |
|
// ( when the duck timer finishes, your view will be locked ) |
|
// The intent of the duck timer is to require you to stand up after you've started |
|
// to unduck and to throttle duck spamming. This just enforces the unduck |
|
// requirement. |
|
if ( player->m_Local.m_bDucked && player->m_Local.m_bDucking ) |
|
{ |
|
mv->m_nButtons &= ~IN_DUCK; |
|
} |
|
|
|
// Only allow one duck per air event. |
|
if ( !bOnGround && m_pTFPlayer->m_Shared.AirDuckedCount() >= TF_AIRDUCKED_COUNT ) |
|
{ |
|
mv->m_nButtons &= ~IN_DUCK; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFGameMovement::OnDuck( int nButtonsPressed ) |
|
{ |
|
// Check to see if we are in the air or ducking. |
|
bool bInAir = ( player->GetGroundEntity() == NULL ); |
|
bool bInDuck = ( player->GetFlags() & FL_DUCKING ) ? true : false; |
|
|
|
// XBOX SERVER ONLY |
|
#if !defined(CLIENT_DLL) |
|
if ( IsX360() && nButtonsPressed & IN_DUCK ) |
|
{ |
|
// Hinting logic |
|
if ( player->GetToggledDuckState() && player->m_nNumCrouches < NUM_CROUCH_HINTS ) |
|
{ |
|
UTIL_HudHintText( player, "#Valve_Hint_Crouch" ); |
|
player->m_nNumCrouches++; |
|
} |
|
} |
|
#endif |
|
|
|
// Have the duck button pressed, but the player currently isn't in the duck position. |
|
if ( ( nButtonsPressed & IN_DUCK ) && !bInDuck ) |
|
{ |
|
player->m_Local.m_flDucktime = GAMEMOVEMENT_DUCK_TIME; |
|
player->m_Local.m_bDucking = true; |
|
|
|
if ( m_pTFPlayer->m_Shared.GetAirDash() > 0 ) |
|
{ |
|
m_pTFPlayer->DoAnimationEvent( PLAYERANIMEVENT_DOUBLEJUMP_CROUCH ); |
|
} |
|
} |
|
|
|
// The player is in duck transition and not duck-jumping. |
|
if ( player->m_Local.m_bDucking ) |
|
{ |
|
float flDuckMilliseconds = MAX( 0.0f, GAMEMOVEMENT_DUCK_TIME - ( float )player->m_Local.m_flDucktime ); |
|
float flDuckSeconds = flDuckMilliseconds * 0.001f; |
|
|
|
// Finish in duck transition when transition time is over, in "duck", in air. |
|
if ( ( flDuckSeconds > TIME_TO_DUCK ) || bInDuck || bInAir ) |
|
{ |
|
FinishDuck(); |
|
} |
|
else |
|
{ |
|
// Calc parametric time |
|
float flDuckFraction = SimpleSpline( flDuckSeconds / TIME_TO_DUCK ); |
|
SetDuckedEyeOffset( flDuckFraction ); |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFGameMovement::OnUnDuck( int nButtonsReleased ) |
|
{ |
|
// Check to see if we are in the air or ducking. |
|
bool bInAir = ( player->GetGroundEntity() == NULL ); |
|
bool bInDuck = ( player->GetFlags() & FL_DUCKING ) ? true : false; |
|
|
|
// Once the duck button is released, start a timer. The player will not be able to engage in a duck |
|
// until the timer expires. In addition, set that we have ducked in air (will be allowed only once |
|
// while in air). |
|
if ( nButtonsReleased & IN_DUCK ) |
|
{ |
|
m_pTFPlayer->m_Shared.SetDuckTimer( gpGlobals->curtime + TF_TIME_TO_DUCK ); |
|
if ( bInAir ) |
|
{ |
|
// Increment the number of times we have ducked in air. |
|
int nCount = m_pTFPlayer->m_Shared.AirDuckedCount() + 1; |
|
m_pTFPlayer->m_Shared.SetAirDucked( nCount ); |
|
} |
|
} |
|
|
|
// Try to unduck unless automovement is not allowed |
|
// NOTE: When not onground, you can always unduck |
|
if ( player->m_Local.m_bAllowAutoMovement || bInAir || player->m_Local.m_bDucking ) |
|
{ |
|
// We released the duck button, we aren't in "duck" and we are not in the air - start unduck transition. |
|
if ( ( nButtonsReleased & IN_DUCK ) ) |
|
{ |
|
if ( bInDuck ) |
|
{ |
|
player->m_Local.m_flDucktime = GAMEMOVEMENT_DUCK_TIME; |
|
} |
|
else if ( player->m_Local.m_bDucking && !player->m_Local.m_bDucked ) |
|
{ |
|
// Invert time if release before fully ducked!!! |
|
float unduckMilliseconds = 1000.0f * TIME_TO_UNDUCK; |
|
float duckMilliseconds = 1000.0f * TIME_TO_DUCK; |
|
float elapsedMilliseconds = GAMEMOVEMENT_DUCK_TIME - player->m_Local.m_flDucktime; |
|
|
|
float fracDucked = elapsedMilliseconds / duckMilliseconds; |
|
float remainingUnduckMilliseconds = fracDucked * unduckMilliseconds; |
|
|
|
player->m_Local.m_flDucktime = GAMEMOVEMENT_DUCK_TIME - unduckMilliseconds + remainingUnduckMilliseconds; |
|
} |
|
} |
|
|
|
// Check to see if we are capable of unducking. |
|
if ( CanUnduck() ) |
|
{ |
|
// or unducking |
|
if ( ( player->m_Local.m_bDucking || player->m_Local.m_bDucked ) ) |
|
{ |
|
float flDuckMilliseconds = MAX( 0.0f, GAMEMOVEMENT_DUCK_TIME - (float)player->m_Local.m_flDucktime ); |
|
float flDuckSeconds = flDuckMilliseconds * 0.001f; |
|
|
|
// Finish ducking immediately if duck time is over or not on ground |
|
if ( flDuckSeconds > TIME_TO_UNDUCK || bInAir ) |
|
{ |
|
FinishUnDuck(); |
|
} |
|
else |
|
{ |
|
// Calc parametric time |
|
float flDuckFraction = SimpleSpline( 1.0f - ( flDuckSeconds / TIME_TO_UNDUCK ) ); |
|
SetDuckedEyeOffset( flDuckFraction ); |
|
player->m_Local.m_bDucking = true; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
// Still under something where we can't unduck, so make sure we reset this timer so |
|
// that we'll unduck once we exit the tunnel, etc. |
|
if ( player->m_Local.m_flDucktime != GAMEMOVEMENT_DUCK_TIME ) |
|
{ |
|
SetDuckedEyeOffset(1.0f); |
|
player->m_Local.m_flDucktime = GAMEMOVEMENT_DUCK_TIME; |
|
player->m_Local.m_bDucked = true; |
|
player->m_Local.m_bDucking = false; |
|
player->AddFlag( FL_DUCKING ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Crop the speed of the player when ducking and on the ground. |
|
//----------------------------------------------------------------------------- |
|
void CTFGameMovement::HandleDuckingSpeedCrop( void ) |
|
{ |
|
BaseClass::HandleDuckingSpeedCrop(); |
|
|
|
if ( m_iSpeedCropped & SPEED_CROPPED_DUCK ) |
|
{ |
|
if ( m_pTFPlayer->m_Shared.IsLoser() ) |
|
{ |
|
mv->m_flForwardMove *= 0; |
|
mv->m_flSideMove *= 0; |
|
mv->m_flUpMove *= 0; |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: See if duck button is pressed and do the appropriate things |
|
//----------------------------------------------------------------------------- |
|
void CTFGameMovement::Duck( void ) |
|
{ |
|
// Check duck overrides. |
|
DuckOverrides(); |
|
|
|
// Calculate the button state. |
|
int buttonsChanged = ( mv->m_nOldButtons ^ mv->m_nButtons ); // These buttons have changed this frame |
|
int buttonsPressed = buttonsChanged & mv->m_nButtons; // The changed ones still down are "pressed" |
|
int buttonsReleased = buttonsChanged & mv->m_nOldButtons; // The changed ones which were previously down are "released" |
|
if ( mv->m_nButtons & IN_DUCK ) |
|
{ |
|
mv->m_nOldButtons |= IN_DUCK; |
|
} |
|
else |
|
{ |
|
mv->m_nOldButtons &= ~IN_DUCK; |
|
} |
|
|
|
// Handle death. |
|
if ( IsDead() ) |
|
{ |
|
// Reset view offset when dead |
|
Vector vecStandViewOffset = GetPlayerViewOffset( false ); |
|
Vector vecOffset = player->GetViewOffset(); |
|
if ( vecOffset.z != vecStandViewOffset.z ) |
|
{ |
|
vecOffset.z = vecStandViewOffset.z; |
|
player->SetViewOffset( vecOffset ); |
|
} |
|
|
|
return; |
|
} |
|
|
|
// Slow down ducked players. |
|
HandleDuckingSpeedCrop(); |
|
|
|
// If the player is holding down the duck button, the player is in duck transition, ducking, or duck-jumping. |
|
bool bFirstTimePredicted = true; // Assumes we never rerun commands on the server. |
|
#ifdef CLIENT_DLL |
|
bFirstTimePredicted = prediction->IsFirstTimePredicted(); |
|
#endif |
|
|
|
bool bInDuck = ( player->GetFlags() & FL_DUCKING ) ? true : false; |
|
if ( ( mv->m_nButtons & IN_DUCK ) || player->m_Local.m_bDucking || bInDuck ) |
|
{ |
|
if ( ( mv->m_nButtons & IN_DUCK ) ) |
|
{ |
|
// DUCK |
|
OnDuck( buttonsPressed ); |
|
} |
|
else |
|
{ |
|
// UNDUCK (or attempt to...) |
|
OnUnDuck( buttonsReleased ); |
|
} |
|
} |
|
// HACK: (jimd 5/25/2006) we have a reoccuring bug (#50063 in Tracker) where the player's |
|
// view height gets left at the ducked height while the player is standing, but we haven't |
|
// been able to repro it to find the cause. It may be fixed now due to a change I'm |
|
// also making in UpdateDuckJumpEyeOffset but just in case, this code will sense the |
|
// problem and restore the eye to the proper position. It doesn't smooth the transition, |
|
// but it is preferable to leaving the player's view too low. |
|
// |
|
// If the player is still alive and not an observer, check to make sure that |
|
// his view height is at the standing height. |
|
else if ( bFirstTimePredicted && !IsDead() && !player->IsObserver() && !player->IsInAVehicle() && !( TFGameRules() && TFGameRules()->ShowMatchSummary() ) ) |
|
{ |
|
float flOffsetDelta = player->GetViewOffset().z - GetPlayerViewOffset( false ).z; |
|
if ( ( fabs( flOffsetDelta ) > 0.1 ) ) |
|
{ |
|
// we should rarely ever get here, so assert so a coder knows when it happens |
|
AssertMsg2( 0, "Restoring player view height at %i %0.3f\n", gpGlobals->tickcount, gpGlobals->curtime ); |
|
DevMsg( 1, "Restoring player view height at %i %0.3f. Delta: %f.\n", gpGlobals->tickcount, gpGlobals->curtime, flOffsetDelta ); |
|
|
|
// set the eye height to the non-ducked height |
|
SetDuckedEyeOffset(0.0f); |
|
} |
|
} |
|
|
|
if ( tf_duck_debug_spew.GetBool() ) |
|
{ |
|
#ifdef GAME_DLL |
|
engine->Con_NPrintf( 0, "SERVER" ); |
|
engine->Con_NPrintf( 1, "m_flDucktime %3.2f", player->m_Local.m_flDucktime.Get() ); |
|
engine->Con_NPrintf( 2, "m_flDuckJumpTime %3.2f", player->m_Local.m_flDuckJumpTime.Get() ); |
|
engine->Con_NPrintf( 3, "m_bDucked %d", player->m_Local.m_bDucked.Get() ); |
|
engine->Con_NPrintf( 4, "m_bDucking %d", player->m_Local.m_bDucking.Get() ); |
|
engine->Con_NPrintf( 5, "m_bInDuckJump %d", player->m_Local.m_bInDuckJump.Get() ); |
|
engine->Con_NPrintf( 6, "viewoffset %3.2f, %3.2f, %3.2f", player->GetViewOffset().x, player->GetViewOffset().y, player->GetViewOffset().z ); |
|
engine->Con_NPrintf( 7, "IN_DUCK %d", mv->m_nButtons & IN_DUCK ); |
|
engine->Con_NPrintf( 8, "GetDuckTimer %3.2f", Max( 0.f, m_pTFPlayer->m_Shared.GetDuckTimer() - gpGlobals->curtime ) ); |
|
#else |
|
engine->Con_NPrintf( 10 + 0, "CLIENT" ); |
|
engine->Con_NPrintf( 10 + 1, "m_flDucktime %3.2f", player->m_Local.m_flDucktime ); |
|
engine->Con_NPrintf( 10 + 2, "m_flDuckJumpTime %3.2f", player->m_Local.m_flDuckJumpTime ); |
|
engine->Con_NPrintf( 10 + 3, "m_bDucked %d", player->m_Local.m_bDucked ); |
|
engine->Con_NPrintf( 10 + 4, "m_bDucking %d", player->m_Local.m_bDucking ); |
|
engine->Con_NPrintf( 10 + 5, "m_bInDuckJump %d", player->m_Local.m_bInDuckJump ); |
|
engine->Con_NPrintf( 10 + 6, "viewoffset %3.2f, %3.2f, %3.2f", player->GetViewOffset().x, player->GetViewOffset().y, player->GetViewOffset().z ); |
|
engine->Con_NPrintf( 10 + 7, "IN_DUCK %d", mv->m_nButtons & IN_DUCK ); |
|
engine->Con_NPrintf( 10 + 8, "GetDuckTimer %3.2f", Max( 0.f, m_pTFPlayer->m_Shared.GetDuckTimer() - gpGlobals->curtime ) ); |
|
#endif |
|
} |
|
} |
|
|
|
#ifdef STAGING_ONLY |
|
//----------------------------------------------------------------------------- |
|
// Purpose: See if the player's double tapped movement keys |
|
//----------------------------------------------------------------------------- |
|
void CTFGameMovement::CheckForDoubleTap( void ) |
|
{ |
|
float flMaxDoubleTapTimeDelta = tf_movement_doubletap_window.GetFloat(); |
|
|
|
static const int aMoveType[4] = |
|
{ |
|
IN_MOVELEFT, |
|
IN_MOVERIGHT, |
|
IN_FORWARD, |
|
IN_BACK, |
|
// Add movetypes here |
|
}; |
|
|
|
for ( int i = 0; i < ARRAYSIZE( aMoveType ); ++i ) |
|
{ |
|
// Record when they let go of the key |
|
if ( ( mv->m_nOldButtons & aMoveType[i] ) && !( mv->m_nButtons & aMoveType[i] ) ) |
|
{ |
|
int index = m_MoveKeyDownTimes.Find( aMoveType[i] ); |
|
if ( index != m_MoveKeyDownTimes.InvalidIndex() ) |
|
{ |
|
m_MoveKeyDownTimes[index] = gpGlobals->curtime; |
|
} |
|
else |
|
{ |
|
// Init |
|
m_MoveKeyDownTimes.Insert( aMoveType[i], gpGlobals->curtime ); |
|
} |
|
} |
|
// If the button is down now, and wasn't before... |
|
else if ( ( mv->m_nButtons & aMoveType[i] ) && !( mv->m_nOldButtons & aMoveType[i] ) ) |
|
{ |
|
int index = m_MoveKeyDownTimes.Find( aMoveType[i] ); |
|
if ( index != m_MoveKeyDownTimes.InvalidIndex() ) |
|
{ |
|
// ...check the time delta - if it's within range, consider it a double-tap. |
|
if ( gpGlobals->curtime - m_MoveKeyDownTimes[index] <= flMaxDoubleTapTimeDelta ) |
|
{ |
|
OnDoubleTapped( aMoveType[i] ); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: See if the player's double tapped movement keys |
|
//----------------------------------------------------------------------------- |
|
void CTFGameMovement::OnDoubleTapped( int nKey ) |
|
{ |
|
int iTeleportMove = 0; |
|
CALL_ATTRIB_HOOK_INT_ON_OTHER( m_pTFPlayer, iTeleportMove, ability_doubletap_teleport ); |
|
if ( iTeleportMove ) |
|
{ |
|
Vector vecDir, vecForward, vecRight; |
|
AngleVectors( m_pTFPlayer->GetAbsAngles(), &vecForward, &vecRight, NULL ); |
|
|
|
if ( nKey == IN_MOVELEFT ) |
|
{ |
|
vecRight.Negate(); |
|
TeleportMove( vecRight, 192.f ); |
|
} |
|
else if ( nKey == IN_MOVERIGHT ) |
|
{ |
|
TeleportMove( vecRight, 192.f ); |
|
} |
|
else if ( nKey == IN_FORWARD ) |
|
{ |
|
TeleportMove( vecForward, 192.f ); |
|
} |
|
else if ( nKey == IN_BACK ) |
|
{ |
|
vecForward.Negate(); |
|
TeleportMove( vecForward, 192.f ); |
|
} |
|
} |
|
|
|
// DevMsg( "Double Tap! (%i)\n", nKey ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CTFGameMovement::TeleportMove( Vector &vecDirection, float flDist ) |
|
{ |
|
if ( m_flNextDoubleTapTeleportTime > gpGlobals->curtime ) |
|
return; |
|
|
|
trace_t result; |
|
CTraceFilterIgnoreTeammates traceFilter( m_pTFPlayer, COLLISION_GROUP_PLAYER_MOVEMENT, m_pTFPlayer->GetTeamNumber() ); |
|
unsigned int nMask = m_pTFPlayer->GetTeamNumber() == TF_TEAM_RED ? CONTENTS_BLUETEAM : CONTENTS_REDTEAM; |
|
nMask |= MASK_PLAYERSOLID; |
|
|
|
// Try full distance |
|
Vector vecPos = mv->GetAbsOrigin() + vecDirection * flDist; |
|
UTIL_TraceHull( mv->GetAbsOrigin(), vecPos, VEC_HULL_MIN_SCALED( m_pTFPlayer ), VEC_HULL_MAX_SCALED( m_pTFPlayer ), nMask, &traceFilter, &result ); |
|
if ( result.DidHit() ) |
|
{ |
|
if ( result.fraction <= 0.2f ) |
|
return; |
|
|
|
vecPos = mv->GetAbsOrigin() + ( ( vecPos - mv->GetAbsOrigin() ) * result.fraction ); |
|
// NDebugOverlay::SweptBox( mv->GetAbsOrigin(), vecPos, VEC_HULL_MIN_SCALED( m_pTFPlayer ), VEC_HULL_MAX_SCALED( m_pTFPlayer ), m_pTFPlayer->GetAbsAngles(), 255, 0, 0, 40, 5.f ); |
|
} |
|
|
|
// Go there |
|
mv->SetAbsOrigin( vecPos ); |
|
|
|
#ifdef GAME_DLL |
|
// Screen flash |
|
color32 fadeColor = { 255, 255, 255, 50 }; |
|
UTIL_ScreenFade( m_pTFPlayer, fadeColor, 0.25f, 0.4f, FFADE_IN ); |
|
|
|
if ( TFGameRules() ) |
|
{ |
|
TFGameRules()->HaveAllPlayersSpeakConceptIfAllowed( MP_CONCEPT_PLAYER_SPELL_TELEPORT, ( m_pTFPlayer->GetTeamNumber() == TF_TEAM_RED ) ? TF_TEAM_BLUE : TF_TEAM_RED ); |
|
} |
|
#endif // GAME_DLL |
|
|
|
// Cooldown |
|
m_flNextDoubleTapTeleportTime = gpGlobals->curtime + 2.f; |
|
} |
|
#endif // STAGING_ONLY
|
|
|