3103 lines
78 KiB
C++
3103 lines
78 KiB
C++
/***
|
|
*
|
|
* Copyright (c) 1996-2002, Valve LLC. All rights reserved.
|
|
*
|
|
* This product contains software technology licensed from Id
|
|
* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc.
|
|
* All Rights Reserved.
|
|
*
|
|
* Use, distribution, and modification of this source code and/or resulting
|
|
* object code is restricted to non-commercial enhancements to products from
|
|
* Valve LLC. All other use, distribution, or modification is prohibited
|
|
* without written permission from Valve LLC.
|
|
*
|
|
****/
|
|
|
|
#include <assert.h>
|
|
#include "basetypes.h"
|
|
#include "const.h"
|
|
#include "vector.h"
|
|
#include "usercmd.h"
|
|
#include "pm_defs.h"
|
|
#include "pm_local.h"
|
|
#include "pm_shared.h"
|
|
#include "pm_movevars.h"
|
|
#include "pm_materials.h"
|
|
#include <stdio.h> // NULL
|
|
#include <math.h> // sqrt
|
|
#include <string.h> // strcpy
|
|
#include <stdlib.h> // atoi
|
|
#include <ctype.h> // isspace
|
|
|
|
static int pm_shared_initialized = 0;
|
|
|
|
#pragma warning( disable : 4305 )
|
|
#pragma warning( disable : 4244 ) // double to float warning
|
|
|
|
playermove_t *pmove = (playermove_t *)NULL;
|
|
|
|
// Ducking time
|
|
#define TIME_TO_DUCK 0.4
|
|
#define VEC_DUCK_HULL_MIN -18
|
|
#define VEC_DUCK_HULL_MAX 18
|
|
#define VEC_DUCK_VIEW 12
|
|
#define PM_DEAD_VIEWHEIGHT -8
|
|
#define MAX_CLIMB_SPEED 200
|
|
#define STUCK_MOVEUP 1
|
|
#define STUCK_MOVEDOWN -1
|
|
#define VEC_HULL_MIN -36
|
|
#define VEC_HULL_MAX 36
|
|
#define VEC_VIEW 28
|
|
#define WJ_HEIGHT 8
|
|
#define STOP_EPSILON 0.1
|
|
#define MAX_CLIENTS 32
|
|
#define CTEXTURESMAX 512 // max number of textures loaded
|
|
#define pev (&pmove->player->v)
|
|
|
|
#define BUNNYJUMP_MAX_SPEED_FACTOR 1.7f // Only allow bunny jumping up to 1.7x server / player maxspeed setting
|
|
#define PM_CHECKSTUCK_MINTIME 0.05f // Don't check again too quickly.
|
|
#define PLAYER_FATAL_FALL_SPEED 1024 // approx 60 feet
|
|
#define PLAYER_MAX_SAFE_FALL_SPEED 580 // approx 20 feet
|
|
#define DAMAGE_FOR_FALL_SPEED (float)100 / ( PLAYER_FATAL_FALL_SPEED - PLAYER_MAX_SAFE_FALL_SPEED )// damage per unit per second.
|
|
#define PLAYER_MIN_BOUNCE_SPEED 200
|
|
#define PLAYER_FALL_PUNCH_THRESHHOLD (float)350 // won't punch player's screen/make scrape noise unless player falling at least this fast.
|
|
#define PLAYER_LONGJUMP_SPEED 350 // how fast we longjump
|
|
|
|
#define max( a, b ) (((a) > (b)) ? (a) : (b))
|
|
#define min( a, b ) (((a) < (b)) ? (a) : (b))
|
|
|
|
static vec3_t rgv3tStuckTable[54];
|
|
static int rgStuckLast[MAX_CLIENTS][2];
|
|
|
|
// Texture names
|
|
static int gcTextures = 0;
|
|
static char grgszTextureName[CTEXTURESMAX][CBTEXTURENAMEMAX];
|
|
static char grgchTextureType[CTEXTURESMAX];
|
|
|
|
int g_onladder = 0;
|
|
|
|
void PM_SwapTextures( int i, int j )
|
|
{
|
|
char chTemp;
|
|
char szTemp[CBTEXTURENAMEMAX];
|
|
|
|
strcpy( szTemp, grgszTextureName[i] );
|
|
chTemp = grgchTextureType[i];
|
|
|
|
strcpy( grgszTextureName[i], grgszTextureName[j] );
|
|
grgchTextureType[i] = grgchTextureType[j];
|
|
|
|
strcpy( grgszTextureName[j], szTemp );
|
|
grgchTextureType[j] = chTemp;
|
|
}
|
|
|
|
void PM_SortTextures( void )
|
|
{
|
|
// Bubble sort, yuck, but this only occurs at startup and it's only 512 elements...
|
|
int i, j;
|
|
|
|
for( i = 0 ; i < gcTextures; i++ )
|
|
{
|
|
for( j = i + 1; j < gcTextures; j++ )
|
|
{
|
|
if( stricmp( grgszTextureName[i], grgszTextureName[j] ) > 0 )
|
|
PM_SwapTextures( i, j ); // swap
|
|
}
|
|
}
|
|
}
|
|
|
|
void PM_InitTextureTypes( void )
|
|
{
|
|
int i, j;
|
|
byte *pMemFile;
|
|
char buffer[512];
|
|
int fileSize, filePos;
|
|
static BOOL bTextureTypeInit = false;
|
|
|
|
if( bTextureTypeInit )
|
|
return;
|
|
|
|
memset( grgszTextureName, 0, CTEXTURESMAX * CBTEXTURENAMEMAX );
|
|
memset( grgchTextureType, 0, CTEXTURESMAX );
|
|
|
|
gcTextures = 0;
|
|
memset( buffer, 0, 512 );
|
|
|
|
pMemFile = pmove->COM_LoadFile( "sound/materials.txt", &fileSize );
|
|
if( !pMemFile ) return;
|
|
|
|
filePos = 0;
|
|
|
|
// for each line in the file...
|
|
while( pmove->memfgets( pMemFile, fileSize, &filePos, buffer, 511 ) != NULL && (gcTextures < CTEXTURESMAX ))
|
|
{
|
|
// skip whitespace
|
|
i = 0;
|
|
while( buffer[i] && isspace( buffer[i] ))
|
|
i++;
|
|
|
|
if ( !buffer[i] )
|
|
continue;
|
|
|
|
// skip comment lines
|
|
if( buffer[i] == '/' || !isalpha( buffer[i] ))
|
|
continue;
|
|
|
|
// get texture type
|
|
grgchTextureType[gcTextures] = toupper( buffer[i++] );
|
|
|
|
// skip whitespace
|
|
while( buffer[i] && isspace( buffer[i] ))
|
|
i++;
|
|
|
|
if ( !buffer[i] )
|
|
continue;
|
|
|
|
// get sentence name
|
|
j = i;
|
|
while ( buffer[j] && !isspace( buffer[j] ))
|
|
j++;
|
|
|
|
if ( !buffer[j] )
|
|
continue;
|
|
|
|
// null-terminate name and save in sentences array
|
|
j = min( j, CBTEXTURENAMEMAX-1 + i );
|
|
buffer[j] = 0;
|
|
strcpy( &( grgszTextureName[gcTextures++][0] ), &(buffer[i] ));
|
|
}
|
|
|
|
// Must use engine to free since we are in a .dll
|
|
pmove->COM_FreeFile ( pMemFile );
|
|
|
|
PM_SortTextures();
|
|
|
|
bTextureTypeInit = true;
|
|
}
|
|
|
|
void PM_PlayGroupSound( const char* szValue, int irand, float fvol )
|
|
{
|
|
static char szBuf[128];
|
|
|
|
for( int i = 0; szValue[i]; i++ )
|
|
{
|
|
if( szValue[i] == '?' )
|
|
{
|
|
strcpy( szBuf, szValue );
|
|
switch( irand )
|
|
{
|
|
// right foot
|
|
case 0: szBuf[i] = '1'; break;
|
|
case 1: szBuf[i] = '3'; break;
|
|
// left foot
|
|
case 2: szBuf[i] = '2'; break;
|
|
case 3: szBuf[i] = '4'; break;
|
|
default: szBuf[i] = '#';break;
|
|
}
|
|
EmitSound( CHAN_BODY, szBuf, fvol, ATTN_NORM, PITCH_NORM );
|
|
return;
|
|
}
|
|
}
|
|
EmitSound( CHAN_BODY, szValue, fvol, ATTN_NORM, PITCH_NORM );
|
|
}
|
|
|
|
char PM_FindTextureType( const char *name )
|
|
{
|
|
int left, right, pivot;
|
|
int val;
|
|
|
|
assert( pm_shared_initialized );
|
|
|
|
left = 0;
|
|
right = gcTextures - 1;
|
|
|
|
while ( left <= right )
|
|
{
|
|
pivot = ( left + right ) / 2;
|
|
|
|
val = strnicmp( name, grgszTextureName[pivot], CBTEXTURENAMEMAX - 1 );
|
|
|
|
if ( val == 0 )
|
|
{
|
|
return grgchTextureType[ pivot ];
|
|
}
|
|
else if ( val > 0 )
|
|
{
|
|
left = pivot + 1;
|
|
}
|
|
else if ( val < 0 )
|
|
{
|
|
right = pivot - 1;
|
|
}
|
|
}
|
|
return CHAR_TEX_CONCRETE;
|
|
}
|
|
|
|
void PM_PlayStepSound( int step, float fvol )
|
|
{
|
|
static int iSkipStep = 0;
|
|
int irand;
|
|
Vector hvel;
|
|
const char *szValue;
|
|
int iType;
|
|
|
|
pev->iStepLeft = !pev->iStepLeft;
|
|
|
|
if ( !pmove->runfuncs )
|
|
{
|
|
return;
|
|
}
|
|
|
|
irand = RANDOM_LONG( 0, 1 ) + ( pev->iStepLeft * 2 );
|
|
|
|
if( pmove->multiplayer && !pmove->movevars->footsteps )
|
|
return;
|
|
|
|
hvel = pmove->velocity;
|
|
hvel.z = 0.0f;
|
|
|
|
if( pmove->multiplayer && ( !g_onladder && hvel.Length() <= 220 ))
|
|
return;
|
|
|
|
switch ( step )
|
|
{
|
|
case STEP_LADDER:
|
|
szValue = Info_ValueForKey( "lsnd" );
|
|
if( szValue[0] && szValue[1] )
|
|
{
|
|
PM_PlayGroupSound( szValue, irand, fvol );
|
|
return;
|
|
}
|
|
break;
|
|
case STEP_SLOSH:
|
|
szValue = Info_ValueForKey( "psnd" );
|
|
if( szValue[0] && szValue[1] )
|
|
{
|
|
PM_PlayGroupSound( szValue, irand, fvol );
|
|
return;
|
|
}
|
|
break;
|
|
case STEP_WADE:
|
|
szValue = Info_ValueForKey( "wsnd" );
|
|
if( szValue[0] && szValue[1] )
|
|
{
|
|
if( iSkipStep == 0 )
|
|
{
|
|
iSkipStep++;
|
|
return;
|
|
}
|
|
|
|
if( iSkipStep++ == 3 ) iSkipStep = 0;
|
|
|
|
PM_PlayGroupSound( szValue, irand, fvol );
|
|
return;
|
|
}
|
|
break;
|
|
default:
|
|
szValue = Info_ValueForKey( "ssnd" );
|
|
if( szValue[0] && szValue[1] )
|
|
{
|
|
PM_PlayGroupSound( szValue, irand, fvol );
|
|
return;
|
|
}
|
|
iType = atoi( Info_ValueForKey( "stype" ));
|
|
if( iType == -1 ) step = STEP_CONCRETE;
|
|
else if( iType ) step = iType;
|
|
}
|
|
|
|
// irand - 0,1 for right foot, 2,3 for left foot
|
|
// used to alternate left and right foot
|
|
// FIXME, move to player state
|
|
|
|
switch( step )
|
|
{
|
|
default:
|
|
case STEP_CONCRETE:
|
|
switch( irand )
|
|
{
|
|
// right foot
|
|
case 0: EmitSound( CHAN_BODY, "player/pl_step1.wav", fvol, ATTN_NORM, PITCH_NORM ); break;
|
|
case 1: EmitSound( CHAN_BODY, "player/pl_step3.wav", fvol, ATTN_NORM, PITCH_NORM ); break;
|
|
// left foot
|
|
case 2: EmitSound( CHAN_BODY, "player/pl_step2.wav", fvol, ATTN_NORM, PITCH_NORM ); break;
|
|
case 3: EmitSound( CHAN_BODY, "player/pl_step4.wav", fvol, ATTN_NORM, PITCH_NORM ); break;
|
|
}
|
|
break;
|
|
case STEP_METAL:
|
|
switch( irand )
|
|
{
|
|
// right foot
|
|
case 0: EmitSound( CHAN_BODY, "player/pl_metal1.wav", fvol, ATTN_NORM, PITCH_NORM ); break;
|
|
case 1: EmitSound( CHAN_BODY, "player/pl_metal3.wav", fvol, ATTN_NORM, PITCH_NORM ); break;
|
|
// left foot
|
|
case 2: EmitSound( CHAN_BODY, "player/pl_metal2.wav", fvol, ATTN_NORM, PITCH_NORM ); break;
|
|
case 3: EmitSound( CHAN_BODY, "player/pl_metal4.wav", fvol, ATTN_NORM, PITCH_NORM ); break;
|
|
}
|
|
break;
|
|
case STEP_DIRT:
|
|
switch( irand )
|
|
{
|
|
// right foot
|
|
case 0: EmitSound( CHAN_BODY, "player/pl_dirt1.wav", fvol, ATTN_NORM, PITCH_NORM ); break;
|
|
case 1: EmitSound( CHAN_BODY, "player/pl_dirt3.wav", fvol, ATTN_NORM, PITCH_NORM ); break;
|
|
// left foot
|
|
case 2: EmitSound( CHAN_BODY, "player/pl_dirt2.wav", fvol, ATTN_NORM, PITCH_NORM ); break;
|
|
case 3: EmitSound( CHAN_BODY, "player/pl_dirt4.wav", fvol, ATTN_NORM, PITCH_NORM ); break;
|
|
}
|
|
break;
|
|
case STEP_VENT:
|
|
switch( irand )
|
|
{
|
|
// right foot
|
|
case 0: EmitSound( CHAN_BODY, "player/pl_duct1.wav", fvol, ATTN_NORM, PITCH_NORM ); break;
|
|
case 1: EmitSound( CHAN_BODY, "player/pl_duct3.wav", fvol, ATTN_NORM, PITCH_NORM ); break;
|
|
// left foot
|
|
case 2: EmitSound( CHAN_BODY, "player/pl_duct2.wav", fvol, ATTN_NORM, PITCH_NORM ); break;
|
|
case 3: EmitSound( CHAN_BODY, "player/pl_duct4.wav", fvol, ATTN_NORM, PITCH_NORM ); break;
|
|
}
|
|
break;
|
|
case STEP_GRATE:
|
|
switch( irand )
|
|
{
|
|
// right foot
|
|
case 0: EmitSound( CHAN_BODY, "player/pl_grate1.wav", fvol, ATTN_NORM, PITCH_NORM ); break;
|
|
case 1: EmitSound( CHAN_BODY, "player/pl_grate3.wav", fvol, ATTN_NORM, PITCH_NORM ); break;
|
|
// left foot
|
|
case 2: EmitSound( CHAN_BODY, "player/pl_grate2.wav", fvol, ATTN_NORM, PITCH_NORM ); break;
|
|
case 3: EmitSound( CHAN_BODY, "player/pl_grate4.wav", fvol, ATTN_NORM, PITCH_NORM ); break;
|
|
}
|
|
break;
|
|
case STEP_TILE:
|
|
if( !RANDOM_LONG( 0, 4 ))
|
|
irand = 4;
|
|
switch( irand )
|
|
{
|
|
// right foot
|
|
case 0: EmitSound( CHAN_BODY, "player/pl_tile1.wav", fvol, ATTN_NORM, PITCH_NORM ); break;
|
|
case 1: EmitSound( CHAN_BODY, "player/pl_tile3.wav", fvol, ATTN_NORM, PITCH_NORM ); break;
|
|
// left foot
|
|
case 2: EmitSound( CHAN_BODY, "player/pl_tile2.wav", fvol, ATTN_NORM, PITCH_NORM ); break;
|
|
case 3: EmitSound( CHAN_BODY, "player/pl_tile4.wav", fvol, ATTN_NORM, PITCH_NORM ); break;
|
|
case 4: EmitSound( CHAN_BODY, "player/pl_tile5.wav", fvol, ATTN_NORM, PITCH_NORM ); break;
|
|
}
|
|
break;
|
|
case STEP_SLOSH:
|
|
switch( irand )
|
|
{
|
|
// right foot
|
|
case 0: EmitSound( CHAN_BODY, "player/pl_slosh1.wav", fvol, ATTN_NORM, PITCH_NORM ); break;
|
|
case 1: EmitSound( CHAN_BODY, "player/pl_slosh3.wav", fvol, ATTN_NORM, PITCH_NORM ); break;
|
|
// left foot
|
|
case 2: EmitSound( CHAN_BODY, "player/pl_slosh2.wav", fvol, ATTN_NORM, PITCH_NORM ); break;
|
|
case 3: EmitSound( CHAN_BODY, "player/pl_slosh4.wav", fvol, ATTN_NORM, PITCH_NORM ); break;
|
|
}
|
|
break;
|
|
case STEP_WADE:
|
|
if( iSkipStep == 0 )
|
|
{
|
|
iSkipStep++;
|
|
break;
|
|
}
|
|
|
|
if( iSkipStep++ == 3 )
|
|
{
|
|
iSkipStep = 0;
|
|
}
|
|
|
|
switch( irand )
|
|
{
|
|
// right foot
|
|
case 0: EmitSound( CHAN_BODY, "player/pl_wade1.wav", fvol, ATTN_NORM, PITCH_NORM ); break;
|
|
case 1: EmitSound( CHAN_BODY, "player/pl_wade2.wav", fvol, ATTN_NORM, PITCH_NORM ); break;
|
|
// left foot
|
|
case 2: EmitSound( CHAN_BODY, "player/pl_wade3.wav", fvol, ATTN_NORM, PITCH_NORM ); break;
|
|
case 3: EmitSound( CHAN_BODY, "player/pl_wade4.wav", fvol, ATTN_NORM, PITCH_NORM ); break;
|
|
}
|
|
break;
|
|
case STEP_LADDER:
|
|
switch( irand )
|
|
{
|
|
// right foot
|
|
case 0: EmitSound( CHAN_BODY, "player/pl_ladder1.wav", fvol, ATTN_NORM, PITCH_NORM ); break;
|
|
case 1: EmitSound( CHAN_BODY, "player/pl_ladder3.wav", fvol, ATTN_NORM, PITCH_NORM ); break;
|
|
// left foot
|
|
case 2: EmitSound( CHAN_BODY, "player/pl_ladder2.wav", fvol, ATTN_NORM, PITCH_NORM ); break;
|
|
case 3: EmitSound( CHAN_BODY, "player/pl_ladder4.wav", fvol, ATTN_NORM, PITCH_NORM ); break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
int PM_MapTextureTypeStepType( char chTextureType )
|
|
{
|
|
switch( chTextureType )
|
|
{
|
|
default:
|
|
case CHAR_TEX_CONCRETE: return STEP_CONCRETE;
|
|
case CHAR_TEX_METAL: return STEP_METAL;
|
|
case CHAR_TEX_DIRT: return STEP_DIRT;
|
|
case CHAR_TEX_VENT: return STEP_VENT;
|
|
case CHAR_TEX_GRATE: return STEP_GRATE;
|
|
case CHAR_TEX_TILE: return STEP_TILE;
|
|
case CHAR_TEX_SLOSH: return STEP_SLOSH;
|
|
}
|
|
}
|
|
|
|
/*
|
|
====================
|
|
PM_CatagorizeTextureType
|
|
|
|
Determine texture info for the texture we are standing on.
|
|
====================
|
|
*/
|
|
void PM_CatagorizeTextureType( void )
|
|
{
|
|
Vector start, end;
|
|
const char *pTextureName;
|
|
|
|
start = end = pmove->origin;
|
|
|
|
// Straight down
|
|
end.z -= 64;
|
|
|
|
// fill in default values, just in case.
|
|
pmove->szTextureName[0] = '\0';
|
|
pmove->chTextureType = CHAR_TEX_CONCRETE;
|
|
|
|
pTextureName = TRACE_TEXTURE( pmove->onground, start, end );
|
|
if ( !pTextureName )
|
|
return;
|
|
|
|
// strip leading '-0' or '+0~' or '{' or '!'
|
|
if( *pTextureName == '-' || *pTextureName == '+' )
|
|
pTextureName += 2;
|
|
|
|
if( *pTextureName == '{' || *pTextureName == '!' || *pTextureName == '~' || *pTextureName == ' ' )
|
|
pTextureName++;
|
|
// '}}'
|
|
|
|
strcpy( pmove->szTextureName, pTextureName );
|
|
pmove->szTextureName[CBTEXTURENAMEMAX - 1] = 0;
|
|
|
|
// get texture type
|
|
pmove->chTextureType = PM_FindTextureType( pmove->szTextureName );
|
|
}
|
|
|
|
void PM_UpdateStepSound( void )
|
|
{
|
|
int fWalking;
|
|
float fvol;
|
|
Vector knee;
|
|
Vector feet;
|
|
Vector center;
|
|
float height;
|
|
float speed;
|
|
float velrun;
|
|
float velwalk;
|
|
float flduck;
|
|
int fLadder;
|
|
int step;
|
|
|
|
if( pev->flTimeStepSound > 0 )
|
|
return;
|
|
|
|
if( pev->flags & FL_FROZEN )
|
|
return;
|
|
|
|
PM_CatagorizeTextureType();
|
|
|
|
speed = pmove->velocity.Length();
|
|
|
|
// determine if we are on a ladder
|
|
fLadder = ( pev->movetype == MOVETYPE_FLY );// IsOnLadder();
|
|
|
|
// UNDONE: need defined numbers for run, walk, crouch, crouch run velocities!!!!
|
|
if(( pev->flags & FL_DUCKING) || fLadder )
|
|
{
|
|
velwalk = 60; // These constants should be based on cl_movespeedkey * cl_forwardspeed somehow
|
|
velrun = 80; // UNDONE: Move walking to server
|
|
flduck = 100;
|
|
}
|
|
else
|
|
{
|
|
velwalk = 120;
|
|
velrun = 210;
|
|
flduck = 0;
|
|
}
|
|
|
|
// If we're on a ladder or on the ground, and we're moving fast enough,
|
|
// play step sound. Also, if pev->flTimeStepSound is zero, get the new
|
|
// sound right away - we just started moving in new level.
|
|
if(( fLadder || ( pmove->onground != NULL )) && ( pmove->velocity.Length() > 0.0f ) && ( speed >= velwalk || !pev->flTimeStepSound ))
|
|
{
|
|
fWalking = (speed < velrun);
|
|
|
|
center = knee = feet = pmove->origin;
|
|
|
|
height = pmove->player_maxs[pmove->usehull].z - pmove->player_mins[pmove->usehull].z;
|
|
|
|
knee[2] = pmove->origin.z - 0.3f * height;
|
|
feet[2] = pmove->origin.z - 0.5f * height;
|
|
|
|
// find out what we're stepping in or on...
|
|
if( fLadder )
|
|
{
|
|
step = STEP_LADDER;
|
|
fvol = 0.35f;
|
|
pev->flTimeStepSound = 350;
|
|
}
|
|
else if( POINT_CONTENTS( knee ) == CONTENTS_WATER )
|
|
{
|
|
step = STEP_WADE;
|
|
fvol = 0.65f;
|
|
pev->flTimeStepSound = 600;
|
|
}
|
|
else if( POINT_CONTENTS( feet ) == CONTENTS_WATER )
|
|
{
|
|
step = STEP_SLOSH;
|
|
fvol = fWalking ? 0.2 : 0.5;
|
|
pev->flTimeStepSound = fWalking ? 400 : 300;
|
|
}
|
|
else
|
|
{
|
|
// find texture under player, if different from current texture,
|
|
// get material type
|
|
step = PM_MapTextureTypeStepType( pmove->chTextureType );
|
|
|
|
switch( pmove->chTextureType )
|
|
{
|
|
default:
|
|
case CHAR_TEX_CONCRETE:
|
|
fvol = fWalking ? 0.2 : 0.5;
|
|
pev->flTimeStepSound = fWalking ? 400 : 300;
|
|
break;
|
|
case CHAR_TEX_METAL:
|
|
fvol = fWalking ? 0.2 : 0.5;
|
|
pev->flTimeStepSound = fWalking ? 400 : 300;
|
|
break;
|
|
case CHAR_TEX_DIRT:
|
|
fvol = fWalking ? 0.25 : 0.55;
|
|
pev->flTimeStepSound = fWalking ? 400 : 300;
|
|
break;
|
|
case CHAR_TEX_VENT:
|
|
fvol = fWalking ? 0.4 : 0.7;
|
|
pev->flTimeStepSound = fWalking ? 400 : 300;
|
|
break;
|
|
case CHAR_TEX_GRATE:
|
|
fvol = fWalking ? 0.2 : 0.5;
|
|
pev->flTimeStepSound = fWalking ? 400 : 300;
|
|
break;
|
|
case CHAR_TEX_TILE:
|
|
fvol = fWalking ? 0.2 : 0.5;
|
|
pev->flTimeStepSound = fWalking ? 400 : 300;
|
|
break;
|
|
case CHAR_TEX_SLOSH:
|
|
fvol = fWalking ? 0.2 : 0.5;
|
|
pev->flTimeStepSound = fWalking ? 400 : 300;
|
|
break;
|
|
}
|
|
}
|
|
|
|
pev->flTimeStepSound += flduck; // slower step time if ducking
|
|
|
|
// play the sound
|
|
// 35% volume if ducking
|
|
if( pev->flags & FL_DUCKING )
|
|
fvol *= 0.35;
|
|
|
|
PM_PlayStepSound( step, fvol );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
PM_AddToTouched
|
|
|
|
Add's the trace result to touch list, if contact is not already in list.
|
|
================
|
|
*/
|
|
BOOL PM_AddToTouched( edict_t *pHit, Vector impactVelocity )
|
|
{
|
|
int i;
|
|
|
|
if( !pHit || pHit->free ) // sanity check
|
|
return false;
|
|
|
|
for( i = 0; i < pmove->numtouch; i++ )
|
|
{
|
|
if( pmove->touchents[i] == pHit )
|
|
break;
|
|
}
|
|
if( i != pmove->numtouch ) // Already in list.
|
|
return false;
|
|
|
|
if( pmove->numtouch >= MAX_PHYSENTS )
|
|
ALERT( at_error, "Too many entities were touched!\n" );
|
|
|
|
pmove->touchents[pmove->numtouch] = pHit;
|
|
pmove->touchvels[pmove->numtouch] = impactVelocity;
|
|
pmove->numtouch++;
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
PM_CheckVelocity
|
|
|
|
See if the player has a bogus velocity value.
|
|
================
|
|
*/
|
|
void PM_CheckVelocity( void )
|
|
{
|
|
int i;
|
|
|
|
// bound velocity
|
|
for( i = 0; i < 3; i++ )
|
|
{
|
|
// See if it's bogus.
|
|
if( IS_NAN( pmove->velocity[i] ))
|
|
{
|
|
ALERT( at_console, "Got a NaN velocity on %i\n", i );
|
|
pmove->velocity[i] = 0;
|
|
}
|
|
if (IS_NAN( pmove->origin[i] ))
|
|
{
|
|
ALERT( at_console, "Got a NaN origin on %i\n", i );
|
|
pmove->origin[i] = 0;
|
|
}
|
|
|
|
// Bound it.
|
|
if( pmove->velocity[i] > pmove->movevars->maxvelocity )
|
|
{
|
|
ALERT( at_error, "Got a velocity too high on %i\n", i );
|
|
pmove->velocity[i] = pmove->movevars->maxvelocity;
|
|
}
|
|
else if( pmove->velocity[i] < -pmove->movevars->maxvelocity )
|
|
{
|
|
ALERT( at_error, "Got a velocity too low on %i\n", i );
|
|
pmove->velocity[i] = -pmove->movevars->maxvelocity;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
PM_ClipVelocity
|
|
|
|
Slide off of the impacting object
|
|
returns the blocked flags:
|
|
0x01 == floor
|
|
0x02 == step / wall
|
|
==================
|
|
*/
|
|
int PM_ClipVelocity( Vector in, Vector normal, Vector &out, float overbounce )
|
|
{
|
|
float backoff;
|
|
float change;
|
|
float angle;
|
|
int i, blocked;
|
|
|
|
angle = normal.z;
|
|
|
|
blocked = 0x00; // Assume unblocked.
|
|
if( angle > 0 ) // If the plane that is blocking us has a positive z component, then assume it's a floor.
|
|
blocked |= 0x01;
|
|
if( !angle ) // If the plane has no Z, it is vertical (wall/step)
|
|
blocked |= 0x02;
|
|
|
|
// Determine how far along plane to slide based on incoming direction.
|
|
// Scale by overbounce factor.
|
|
backoff = DotProduct( in, normal ) * overbounce;
|
|
|
|
for( i = 0; i < 3; i++ )
|
|
{
|
|
change = normal[i] * backoff;
|
|
out[i] = in[i] - change;
|
|
|
|
// If out velocity is too small, zero it out.
|
|
if( out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON )
|
|
out[i] = 0;
|
|
}
|
|
|
|
// Return blocking flags.
|
|
return blocked;
|
|
}
|
|
|
|
void PM_AddCorrectGravity( void )
|
|
{
|
|
float ent_gravity;
|
|
|
|
if ( pmove->flWaterJumpTime )
|
|
return;
|
|
|
|
if ( pev->gravity )
|
|
ent_gravity = pev->gravity;
|
|
else ent_gravity = 1.0f;
|
|
|
|
// Add gravity so they'll be in the correct position during movement
|
|
// yes, this 0.5 looks wrong, but it's not.
|
|
pmove->velocity.z -= (ent_gravity * pmove->movevars->gravity * 0.5f * pmove->frametime );
|
|
pmove->velocity.z += pmove->basevelocity.z * pmove->frametime;
|
|
pmove->basevelocity.z = 0.0f;
|
|
|
|
PM_CheckVelocity();
|
|
}
|
|
|
|
|
|
void PM_FixupGravityVelocity( void )
|
|
{
|
|
float ent_gravity;
|
|
|
|
if ( pmove->flWaterJumpTime )
|
|
return;
|
|
|
|
if ( pev->gravity )
|
|
ent_gravity = pev->gravity;
|
|
else ent_gravity = 1.0f;
|
|
|
|
// Get the correct velocity for the end of the dt
|
|
pmove->velocity.z -= (ent_gravity * pmove->movevars->gravity * pmove->frametime * 0.5f );
|
|
|
|
PM_CheckVelocity();
|
|
}
|
|
|
|
/*
|
|
============
|
|
PM_FlyMove
|
|
|
|
The basic solid body movement clip that slides along multiple planes
|
|
============
|
|
*/
|
|
int PM_FlyMove( void )
|
|
{
|
|
int bumpcount, numbumps;
|
|
Vector dir;
|
|
float d;
|
|
int numplanes;
|
|
Vector planes[MAX_CLIP_PLANES];
|
|
Vector primal_velocity, original_velocity;
|
|
Vector new_velocity;
|
|
int i, j;
|
|
TraceResult trace;
|
|
Vector end;
|
|
float time_left, allFraction;
|
|
int blocked;
|
|
|
|
numbumps = 4; // Bump up to four times
|
|
|
|
blocked = 0; // Assume not blocked
|
|
numplanes = 0; // and not sliding along any planes
|
|
|
|
original_velocity = primal_velocity = pmove->velocity; // Store original velocity
|
|
|
|
allFraction = 0;
|
|
time_left = pmove->frametime; // Total time for this movement operation.
|
|
|
|
for( bumpcount = 0; bumpcount < numbumps; bumpcount++ )
|
|
{
|
|
if( pmove->velocity == g_vecZero )
|
|
break;
|
|
|
|
// Assume we can move all the way from the current origin to the
|
|
// end point.
|
|
end = pmove->origin + pmove->velocity * time_left;
|
|
|
|
// See if we can make it from origin to end point.
|
|
trace = TRACE_PLAYER( pmove->origin, end, PM_NORMAL );
|
|
|
|
allFraction += trace.flFraction;
|
|
|
|
// If we started in a solid object, or we were in solid space
|
|
// the whole way, zero out our velocity and return that we
|
|
// are blocked by floor and wall.
|
|
if( trace.fAllSolid )
|
|
{
|
|
// entity is trapped in another solid
|
|
pmove->velocity = g_vecZero;
|
|
return 4;
|
|
}
|
|
|
|
// If we moved some portion of the total distance, then
|
|
// copy the end position into the pmove->origin and
|
|
// zero the plane counter.
|
|
if( trace.flFraction > 0.0f )
|
|
{
|
|
// actually covered some distance
|
|
pmove->origin = trace.vecEndPos;
|
|
original_velocity = pmove->velocity;
|
|
numplanes = 0;
|
|
}
|
|
|
|
// If we covered the entire distance, we are done
|
|
// and can return.
|
|
if( trace.flFraction == 1.0f )
|
|
break; // moved the entire distance
|
|
|
|
if( !trace.pHit ) trace.pHit = ENTINDEX( 0 ); // world
|
|
|
|
// Save entity that blocked us (since fraction was < 1.0)
|
|
// for contact
|
|
// Add it if it's not already in the list!!!
|
|
PM_AddToTouched( trace.pHit, pmove->velocity );
|
|
|
|
// If the plane we hit has a high z component in the normal, then
|
|
// it's probably a floor
|
|
if( trace.vecPlaneNormal.z > 0.7f )
|
|
{
|
|
blocked |= 1; // floor
|
|
}
|
|
// If the plane has a zero z component in the normal, then it's a
|
|
// step or wall
|
|
if( trace.vecPlaneNormal.z == 0.0f )
|
|
{
|
|
blocked |= 2; // step / wall
|
|
// ALERT( at_console, "Blocked by %s\n", STRING( trace.pHit->v.classname ));
|
|
}
|
|
|
|
// Reduce amount of pmove->frametime left by total time left * fraction
|
|
// that we covered.
|
|
time_left -= time_left * trace.flFraction;
|
|
|
|
// Did we run out of planes to clip against?
|
|
if( numplanes >= MAX_CLIP_PLANES )
|
|
{
|
|
// this shouldn't really happen
|
|
// Stop our movement if so.
|
|
pmove->velocity = g_vecZero;
|
|
// ALERT( at_console, "Too many planes %i\n", MAX_CLIP_PLANES );
|
|
break;
|
|
}
|
|
|
|
// Set up next clipping plane
|
|
planes[numplanes] = trace.vecPlaneNormal;
|
|
numplanes++;
|
|
|
|
// modify original_velocity so it parallels all of the clip planes
|
|
if( pev->movetype == MOVETYPE_WALK && (( pmove->onground == NULL ) || ( pev->friction != 1.0f )))
|
|
{
|
|
// relfect player velocity
|
|
for ( i = 0; i < numplanes; i++ )
|
|
{
|
|
if( planes[i][2] > 0.7f )
|
|
{
|
|
// floor or slope
|
|
PM_ClipVelocity( original_velocity, planes[i], new_velocity, 1.0f );
|
|
original_velocity = new_velocity;
|
|
}
|
|
else
|
|
{
|
|
float overClip = 1.0f + pmove->movevars->bounce * (1.0f - pev->friction );
|
|
PM_ClipVelocity( original_velocity, planes[i], new_velocity, overClip );
|
|
}
|
|
}
|
|
pmove->velocity = original_velocity = new_velocity;
|
|
}
|
|
else
|
|
{
|
|
for ( i = 0; i < numplanes; i++ )
|
|
{
|
|
PM_ClipVelocity( original_velocity, planes[i], pmove->velocity, 1.0f );
|
|
|
|
for ( j = 0; j < numplanes; j++ )
|
|
{
|
|
if ( j != i )
|
|
{
|
|
// Are we now moving against this plane?
|
|
if( DotProduct( pmove->velocity, planes[j] ) < 0.0f )
|
|
break; // not ok
|
|
}
|
|
}
|
|
if ( j == numplanes ) // Didn't have to clip, so we're ok
|
|
break;
|
|
}
|
|
|
|
// Did we go all the way through plane set
|
|
if ( i != numplanes )
|
|
{
|
|
// go along this plane
|
|
// pmove->velocity is set in clipping call, no need to set again.
|
|
}
|
|
else
|
|
{
|
|
// go along the crease
|
|
if ( numplanes != 2 )
|
|
{
|
|
//ALERT( at_console, "clip velocity, numplanes == %i\n", numplanes );
|
|
pmove->velocity = g_vecZero;
|
|
break;
|
|
}
|
|
|
|
dir = CrossProduct( planes[0], planes[1] );
|
|
d = DotProduct( dir, pmove->velocity );
|
|
pmove->velocity = dir * d;
|
|
}
|
|
|
|
// if original velocity is against the original velocity, stop dead
|
|
// to avoid tiny occilations in sloping corners
|
|
if ( DotProduct( pmove->velocity, primal_velocity ) <= 0.0f )
|
|
{
|
|
pmove->velocity = g_vecZero;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( allFraction == 0 )
|
|
pmove->velocity = g_vecZero;
|
|
|
|
return blocked;
|
|
}
|
|
|
|
/*
|
|
==============
|
|
PM_Accelerate
|
|
==============
|
|
*/
|
|
void PM_Accelerate( Vector wishdir, float wishspeed, float accel )
|
|
{
|
|
float addspeed, accelspeed, currentspeed;
|
|
|
|
// Dead player's don't accelerate
|
|
if ( pmove->dead )
|
|
return;
|
|
|
|
// If waterjumping, don't accelerate
|
|
if ( pmove->flWaterJumpTime )
|
|
return;
|
|
|
|
// See if we are changing direction a bit
|
|
currentspeed = DotProduct( pmove->velocity, wishdir );
|
|
|
|
// Reduce wishspeed by the amount of veer.
|
|
addspeed = wishspeed - currentspeed;
|
|
|
|
// If not going to add any speed, done.
|
|
if ( addspeed <= 0 )
|
|
return;
|
|
|
|
// Determine amount of accleration.
|
|
accelspeed = accel * pmove->frametime * wishspeed * pev->friction;
|
|
|
|
// Cap at addspeed
|
|
if ( accelspeed > addspeed )
|
|
accelspeed = addspeed;
|
|
|
|
// Adjust velocity.
|
|
pmove->velocity += accelspeed * wishdir;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
PM_WalkMove
|
|
|
|
Only used by players. Moves along the ground when player is a MOVETYPE_WALK.
|
|
======================
|
|
*/
|
|
void PM_WalkMove( void )
|
|
{
|
|
int clip;
|
|
edict_t *oldonground;
|
|
TraceResult trace;
|
|
|
|
Vector wishvel;
|
|
float spd;
|
|
float fmove, smove;
|
|
Vector wishdir;
|
|
float wishspeed;
|
|
|
|
Vector dest, start;
|
|
Vector original, originalvel;
|
|
Vector down, downvel;
|
|
float downdist, updist;
|
|
|
|
// copy movement amounts
|
|
fmove = pmove->cmd.forwardmove;
|
|
smove = pmove->cmd.sidemove;
|
|
|
|
// Zero out z components of movement vectors
|
|
pmove->forward.z = 0.0f;
|
|
pmove->right.z = 0.0f;
|
|
|
|
pmove->forward = pmove->forward.Normalize(); // Normalize remainder of vectors.
|
|
pmove->right = pmove->right.Normalize(); //
|
|
// Determine x and y parts of velocity
|
|
wishvel.x = pmove->forward.x * fmove + pmove->right.x * smove;
|
|
wishvel.y = pmove->forward.y * fmove + pmove->right.y * smove;
|
|
wishvel.z = 0.0f; // Zero out z part of velocity
|
|
|
|
wishdir = wishvel; // Determine maginitude of speed of move
|
|
wishspeed = wishdir.Length();
|
|
wishdir = wishdir.Normalize();
|
|
|
|
// Clamp to server defined max speed
|
|
if( wishspeed > pmove->maxspeed )
|
|
{
|
|
wishvel *= (pmove->maxspeed / wishspeed);
|
|
wishspeed = pmove->maxspeed;
|
|
}
|
|
|
|
// set pmove velocity
|
|
pmove->velocity.z = 0.0f;
|
|
PM_Accelerate( wishdir, wishspeed, pmove->movevars->accelerate );
|
|
pmove->velocity.z = 0.0f;
|
|
|
|
// Add in any base velocity to the current velocity.
|
|
pmove->velocity += pmove->basevelocity;
|
|
|
|
spd = pmove->velocity.Length();
|
|
|
|
if ( spd < 1.0f )
|
|
{
|
|
pmove->velocity = g_vecZero;
|
|
return;
|
|
}
|
|
|
|
// If we are not moving, do nothing
|
|
// if ( pmove->velocity == g_vecZero )
|
|
// return;
|
|
|
|
oldonground = pmove->onground;
|
|
|
|
// first try just moving to the destination
|
|
dest.x = pmove->origin.x + pmove->velocity.x * pmove->frametime;
|
|
dest.y = pmove->origin.y + pmove->velocity.y * pmove->frametime;
|
|
dest.z = pmove->origin.z;
|
|
|
|
// first try moving directly to the next spot
|
|
start = dest;
|
|
trace = TRACE_PLAYER( pmove->origin, dest, PM_NORMAL );
|
|
|
|
// If we made it all the way, then copy trace end
|
|
// as new player position.
|
|
if ( trace.flFraction == 1.0f )
|
|
{
|
|
pmove->origin = trace.vecEndPos;
|
|
return;
|
|
}
|
|
|
|
if ( oldonground == NULL && pev->waterlevel == 0 ) // Don't walk up stairs if not on ground.
|
|
return;
|
|
|
|
if ( pmove->flWaterJumpTime ) // If we are jumping out of water, don't do anything more.
|
|
return;
|
|
|
|
// Try sliding forward both on ground and up 16 pixels
|
|
// take the move that goes farthest
|
|
original = pmove->origin; // Save out original pos &
|
|
originalvel = pmove->velocity; // velocity.
|
|
|
|
// Slide move
|
|
clip = PM_FlyMove ();
|
|
|
|
// Copy the results out
|
|
down = pmove->origin;
|
|
downvel = pmove->velocity;
|
|
|
|
// Reset original values.
|
|
pmove->origin = original;
|
|
pmove->velocity = originalvel;
|
|
|
|
// Start out up one stair height
|
|
dest = pmove->origin;
|
|
dest.z += pmove->movevars->stepsize;
|
|
|
|
trace = TRACE_PLAYER( pmove->origin, dest, PM_NORMAL );
|
|
|
|
// If we started okay and made it part of the way at least,
|
|
// copy the results to the movement start position and then
|
|
// run another move try.
|
|
if ( !trace.fStartSolid && !trace.fAllSolid )
|
|
{
|
|
pmove->origin = trace.vecEndPos;
|
|
}
|
|
|
|
// slide move the rest of the way.
|
|
clip = PM_FlyMove ();
|
|
|
|
// Now try going back down from the end point
|
|
// press down the stepheight
|
|
dest = pmove->origin;
|
|
dest.z -= pmove->movevars->stepsize;
|
|
|
|
trace = TRACE_PLAYER( pmove->origin, dest, PM_NORMAL );
|
|
|
|
// If we are not on the ground any more then
|
|
// use the original movement attempt
|
|
if ( trace.vecPlaneNormal.z < 0.7f )
|
|
goto usedown;
|
|
|
|
// If the trace ended up in empty space, copy the end
|
|
// over to the origin.
|
|
if ( !trace.fStartSolid && !trace.fAllSolid )
|
|
{
|
|
pmove->origin = trace.vecEndPos;
|
|
}
|
|
|
|
// Copy this origin to up.
|
|
pmove->up = pmove->origin;
|
|
|
|
// decide which one went farther
|
|
downdist = (down.x - original.x) * (down.x - original.x) + (down.y - original.y) * (down.y - original.y);
|
|
updist = (pmove->up.x - original.x) * (pmove->up.x - original.x) + (pmove->up.y - original.y) * (pmove->up.y - original.y);
|
|
|
|
if ( downdist > updist )
|
|
{
|
|
usedown:
|
|
pmove->origin = down;
|
|
pmove->velocity = downvel;
|
|
}
|
|
else pmove->velocity.z = downvel.z; // copy z value from slide move
|
|
}
|
|
|
|
/*
|
|
==================
|
|
PM_Friction
|
|
|
|
Handles both ground friction and water friction
|
|
==================
|
|
*/
|
|
void PM_Friction( void )
|
|
{
|
|
float speed, newspeed, control;
|
|
float friction;
|
|
float drop;
|
|
Vector newvel;
|
|
|
|
// If we are in water jump cycle, don't apply friction
|
|
if ( pmove->flWaterJumpTime )
|
|
return;
|
|
|
|
// Calculate speed
|
|
speed = pmove->velocity.Length();
|
|
|
|
// If too slow, return
|
|
if ( speed < 0.1f )
|
|
{
|
|
return;
|
|
}
|
|
|
|
drop = 0;
|
|
|
|
// apply ground friction
|
|
if ( pmove->onground != NULL ) // On an entity that is the ground
|
|
{
|
|
Vector start, stop;
|
|
TraceResult trace;
|
|
|
|
start.x = stop.x = pmove->origin.x + pmove->velocity.x / speed * 16;
|
|
start.y = stop.y = pmove->origin.y + pmove->velocity.y / speed * 16;
|
|
start.z = pmove->origin.z + pmove->player_mins[pmove->usehull].z;
|
|
stop.z = start.z - 34;
|
|
|
|
trace = TRACE_PLAYER( start, stop, PM_NORMAL );
|
|
|
|
if ( trace.flFraction == 1.0f )
|
|
friction = pmove->movevars->friction * pmove->movevars->edgefriction;
|
|
else friction = pmove->movevars->friction;
|
|
|
|
// Grab friction value.
|
|
// friction = pmove->movevars->friction;
|
|
friction *= pev->friction; // player friction?
|
|
|
|
// Bleed off some speed, but if we have less than the bleed
|
|
// threshhold, bleed the theshold amount.
|
|
control = (speed < pmove->movevars->stopspeed) ? pmove->movevars->stopspeed : speed;
|
|
|
|
// Add the amount to t'he drop amount.
|
|
drop += control * friction * pmove->frametime;
|
|
}
|
|
|
|
// apply water friction
|
|
// if ( pev->waterlevel )
|
|
// drop += speed * pmove->movevars->waterfriction * waterlevel * pmove->frametime;
|
|
|
|
// scale the velocity
|
|
newspeed = speed - drop;
|
|
if ( newspeed < 0 ) newspeed = 0;
|
|
|
|
// Determine proportion of old speed we are using.
|
|
newspeed /= speed;
|
|
|
|
// Adjust velocity according to proportion.
|
|
pmove->velocity *= newspeed;
|
|
}
|
|
|
|
void PM_AirAccelerate( Vector wishdir, float wishspeed, float accel )
|
|
{
|
|
float addspeed, accelspeed, currentspeed;
|
|
float wishspd = wishspeed;
|
|
|
|
if ( pmove->dead )
|
|
return;
|
|
if ( pmove->flWaterJumpTime )
|
|
return;
|
|
|
|
// Cap speed
|
|
if( wishspd > 30 ) wishspd = 30;
|
|
// Determine veer amount
|
|
currentspeed = DotProduct( pmove->velocity, wishdir );
|
|
// See how much to add
|
|
addspeed = wishspd - currentspeed;
|
|
// If not adding any, done.
|
|
if ( addspeed <= 0 )
|
|
return;
|
|
// Determine acceleration speed after acceleration
|
|
|
|
accelspeed = accel * wishspeed * pmove->frametime * pev->friction;
|
|
// Cap it
|
|
if ( accelspeed > addspeed )
|
|
accelspeed = addspeed;
|
|
|
|
// Adjust pmove vel.
|
|
pmove->velocity += accelspeed * wishdir;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
PM_WaterMove
|
|
|
|
===================
|
|
*/
|
|
void PM_WaterMove( void )
|
|
{
|
|
Vector temp;
|
|
TraceResult trace;
|
|
Vector start, dest;
|
|
float speed, newspeed;
|
|
Vector wishvel, wishdir;
|
|
float wishspeed, addspeed, accelspeed;
|
|
|
|
// user intentions
|
|
wishvel = pmove->forward * pmove->cmd.forwardmove + pmove->right * pmove->cmd.sidemove;
|
|
|
|
// Sinking after no other movement occurs
|
|
if( !pmove->cmd.forwardmove && !pmove->cmd.sidemove && !pmove->cmd.upmove && pev->watertype != CONTENTS_FLYFIELD )
|
|
wishvel.z -= 60; // drift towards bottom
|
|
else wishvel.z += pmove->cmd.upmove; // Go straight up by upmove amount.
|
|
|
|
// Copy it over and determine speed
|
|
wishdir = wishvel;
|
|
wishspeed = wishdir.Length();
|
|
wishdir = wishdir.Normalize();
|
|
|
|
// Cap speed.
|
|
if( wishspeed > pmove->maxspeed )
|
|
{
|
|
wishvel *= (pmove->maxspeed / wishspeed );
|
|
wishspeed = pmove->maxspeed;
|
|
}
|
|
|
|
// Slow us down a bit.
|
|
wishspeed *= 0.8f;
|
|
|
|
pmove->velocity += pmove->basevelocity;
|
|
|
|
// Water friction
|
|
temp = pmove->velocity;
|
|
speed = temp.Length();
|
|
temp = temp.Normalize();
|
|
|
|
if( speed )
|
|
{
|
|
newspeed = speed - pmove->frametime * speed * pmove->movevars->friction * pev->friction;
|
|
|
|
if( newspeed < 0.0f ) newspeed = 0.0f;
|
|
pmove->velocity *= ( newspeed / speed );
|
|
}
|
|
else newspeed = 0.0f;
|
|
|
|
// water acceleration
|
|
if ( wishspeed < 0.1f )
|
|
{
|
|
return;
|
|
}
|
|
|
|
addspeed = wishspeed - newspeed;
|
|
if ( addspeed > 0.0f )
|
|
{
|
|
wishvel = wishvel.Normalize();
|
|
accelspeed = pmove->movevars->accelerate * wishspeed * pmove->frametime * pev->friction;
|
|
if( accelspeed > addspeed ) accelspeed = addspeed;
|
|
|
|
pmove->velocity += accelspeed * wishvel;
|
|
}
|
|
|
|
// Now move
|
|
// assume it is a stair or a slope, so press down from stepheight above
|
|
dest = pmove->origin + pmove->velocity * pmove->frametime;
|
|
start = dest;
|
|
|
|
start.z += pmove->movevars->stepsize + 1;
|
|
trace = TRACE_PLAYER( start, dest, PM_NORMAL );
|
|
|
|
if( !trace.fStartSolid && !trace.fAllSolid ) // FIXME: check steep slope?
|
|
{
|
|
// walked up the step, so just keep result and exit
|
|
pmove->origin = trace.vecEndPos;
|
|
return;
|
|
}
|
|
|
|
// Try moving straight along out normal path.
|
|
PM_FlyMove ();
|
|
}
|
|
|
|
|
|
/*
|
|
===================
|
|
PM_AirMove
|
|
|
|
===================
|
|
*/
|
|
void PM_AirMove( void )
|
|
{
|
|
Vector wishvel;
|
|
float fmove, smove;
|
|
Vector wishdir;
|
|
float wishspeed;
|
|
|
|
// Copy movement amounts
|
|
fmove = pmove->cmd.forwardmove;
|
|
smove = pmove->cmd.sidemove;
|
|
|
|
// Zero out z components of movement vectors
|
|
pmove->forward.z = 0.0f;
|
|
pmove->right.z = 0.0f;
|
|
|
|
// Renormalize
|
|
pmove->forward = pmove->forward.Normalize();
|
|
pmove->right = pmove->right.Normalize();
|
|
|
|
// Determine x and y parts of velocity
|
|
wishvel.x = pmove->forward.x * fmove + pmove->right.x * smove;
|
|
wishvel.y = pmove->forward.y * fmove + pmove->right.y * smove;
|
|
|
|
// Zero out z part of velocity
|
|
wishvel.z = 0.0f;
|
|
|
|
// Determine maginitude of speed of move
|
|
wishdir = wishvel;
|
|
wishspeed = wishdir.Length();
|
|
wishdir = wishdir.Normalize();
|
|
|
|
// Clamp to server defined max speed
|
|
if( wishspeed > pmove->maxspeed )
|
|
{
|
|
wishvel *= (pmove->maxspeed / wishspeed );
|
|
wishspeed = pmove->maxspeed;
|
|
}
|
|
|
|
PM_AirAccelerate( wishdir, wishspeed, pmove->movevars->airaccelerate );
|
|
|
|
// Add in any base velocity to the current velocity.
|
|
pmove->velocity += pmove->basevelocity;
|
|
|
|
PM_FlyMove ();
|
|
}
|
|
|
|
BOOL PM_InWater( void )
|
|
{
|
|
return ( pev->waterlevel > 1 );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
PM_CheckWater
|
|
|
|
Sets pev->waterlevel and pev->watertype values.
|
|
=============
|
|
*/
|
|
BOOL PM_CheckWater( void )
|
|
{
|
|
Vector point;
|
|
int cont;
|
|
float height;
|
|
float heightover2;
|
|
|
|
// Pick a spot just above the players feet.
|
|
point[0] = pmove->origin[0] + (pmove->player_mins[pmove->usehull][0] + pmove->player_maxs[pmove->usehull][0]) * 0.5f;
|
|
point[1] = pmove->origin[1] + (pmove->player_mins[pmove->usehull][1] + pmove->player_maxs[pmove->usehull][1]) * 0.5f;
|
|
point[2] = pmove->origin[2] + (pmove->player_mins[pmove->usehull][2] + 1);
|
|
|
|
// Assume that we are not in water at all.
|
|
pev->waterlevel = 0;
|
|
pev->watertype = CONTENTS_EMPTY;
|
|
|
|
// Grab point contents.
|
|
cont = POINT_CONTENTS( point );
|
|
|
|
// Are we under water? (not solid and not empty?)
|
|
if ( cont <= CONTENTS_WATER && cont > CONTENTS_TRANSLUCENT )
|
|
{
|
|
// Set water type
|
|
pev->watertype = cont;
|
|
|
|
// We are at least at level one
|
|
pev->waterlevel = 1;
|
|
|
|
height = (pmove->player_mins[pmove->usehull][2] + pmove->player_maxs[pmove->usehull][2]);
|
|
heightover2 = height * 0.5f;
|
|
|
|
// Now check a point that is at the player hull midpoint.
|
|
point[2] = pmove->origin[2] + heightover2;
|
|
cont = POINT_CONTENTS (point );
|
|
|
|
// If that point is also under water...
|
|
if ( cont <= CONTENTS_WATER && cont > CONTENTS_TRANSLUCENT )
|
|
{
|
|
// Set a higher water level.
|
|
pev->waterlevel = 2;
|
|
|
|
// Now check the eye position. (view_ofs is relative to the origin)
|
|
point[2] = pmove->origin[2] + pev->view_ofs[2];
|
|
|
|
cont = POINT_CONTENTS( point );
|
|
if ( cont <= CONTENTS_WATER && cont > CONTENTS_TRANSLUCENT )
|
|
pev->waterlevel = 3; // In over our eyes
|
|
}
|
|
|
|
// Adjust velocity based on water current, if any.
|
|
if (( cont <= CONTENTS_CURRENT_0 ) && ( cont >= CONTENTS_CURRENT_DOWN ))
|
|
{
|
|
// The deeper we are, the stronger the current.
|
|
Vector current_table[] =
|
|
{
|
|
Vector( 1, 0, 0 ),
|
|
Vector( 0, 1, 0 ),
|
|
Vector(-1, 0, 0 ),
|
|
Vector( 0,-1, 0 ),
|
|
Vector( 0, 0, 1 ),
|
|
Vector( 0, 0,-1 )
|
|
};
|
|
|
|
pmove->basevelocity += current_table[CONTENTS_CURRENT_0 - cont] * (50.0f * pev->waterlevel);
|
|
}
|
|
}
|
|
return pev->waterlevel > 1;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
PM_CatagorizePosition
|
|
=============
|
|
*/
|
|
void PM_CatagorizePosition( void )
|
|
{
|
|
Vector point;
|
|
TraceResult tr;
|
|
|
|
// if the player hull point one unit down is solid, the player
|
|
// is on ground
|
|
|
|
// see if standing on something solid
|
|
|
|
// 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.
|
|
PM_CheckWater();
|
|
|
|
point[0] = pmove->origin[0];
|
|
point[1] = pmove->origin[1];
|
|
point[2] = pmove->origin[2] - 2;
|
|
|
|
if ( pmove->velocity.z > 180 ) // Shooting up really fast. Definitely not on ground.
|
|
{
|
|
pmove->onground = (edict_t *)NULL;
|
|
}
|
|
else
|
|
{
|
|
// Try and move down.
|
|
tr = TRACE_PLAYER( pmove->origin, point, PM_NORMAL );
|
|
|
|
// If we hit a steep plane, we are not on ground
|
|
if ( tr.vecPlaneNormal.z < 0.7f )
|
|
pmove->onground = (edict_t *)NULL; // too steep
|
|
else pmove->onground = tr.pHit; // Otherwise, point to index of ent under us.
|
|
|
|
// If we are on something...
|
|
if ( pmove->onground != NULL )
|
|
{
|
|
// Then we are not in water jump sequence
|
|
pmove->flWaterJumpTime = 0;
|
|
// If we could make the move, drop us down that 1 pixel
|
|
if ( pev->waterlevel < 2 && !tr.fStartSolid && !tr.fAllSolid )
|
|
pmove->origin = tr.vecEndPos;
|
|
}
|
|
|
|
// Standing on an entity other than the world
|
|
if ( tr.pHit != NULL && tr.pHit != ENTINDEX( 0 ))
|
|
{
|
|
// So signal that we are touching something.
|
|
PM_AddToTouched( tr.pHit, pmove->velocity );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PM_GetRandomStuckOffsets
|
|
|
|
When a player is stuck, it's costly to try and unstick them
|
|
Grab a test offset for the player based on a passed in index
|
|
=================
|
|
*/
|
|
int PM_GetRandomStuckOffsets( int nIndex, int server, Vector &offset )
|
|
{
|
|
// Last time we did a full
|
|
int idx = rgStuckLast[nIndex][server]++;
|
|
|
|
offset = rgv3tStuckTable[idx % 54];
|
|
|
|
return (idx % 54);
|
|
}
|
|
|
|
void PM_ResetStuckOffsets( int nIndex, int server )
|
|
{
|
|
rgStuckLast[nIndex][server] = 0;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
NudgePosition
|
|
|
|
If pmove->origin is in a solid position,
|
|
try nudging slightly on all axis to
|
|
allow for the cut precision of the net coordinates
|
|
=================
|
|
*/
|
|
int PM_CheckStuck( void )
|
|
{
|
|
Vector base;
|
|
Vector offset;
|
|
Vector test;
|
|
edict_t *hitent;
|
|
int idx;
|
|
float fTime;
|
|
int i;
|
|
TraceResult trace;
|
|
static float rgStuckCheckTime[MAX_CLIENTS][2]; // Last time we did a full
|
|
|
|
// If position is okay, exit
|
|
hitent = TEST_PLAYER( pmove->origin, &trace );
|
|
|
|
if( hitent == NULL )
|
|
{
|
|
PM_ResetStuckOffsets( player_index(), pmove->server );
|
|
return 0;
|
|
}
|
|
|
|
base = pmove->origin;
|
|
|
|
// Deal with precision error in network.
|
|
if ( !pmove->server )
|
|
{
|
|
// World or BSP model
|
|
if ( hitent == ENTINDEX( 0 ) || ( hitent && Mod_GetType( hitent->v.modelindex ) == mod_brush ))
|
|
{
|
|
int nReps = 0;
|
|
|
|
PM_ResetStuckOffsets( player_index(), pmove->server );
|
|
|
|
do
|
|
{
|
|
i = PM_GetRandomStuckOffsets(player_index(), pmove->server, offset);
|
|
|
|
test = base + offset;
|
|
if( TEST_PLAYER( test, &trace ) == NULL )
|
|
{
|
|
PM_ResetStuckOffsets( player_index(), pmove->server );
|
|
pmove->origin = test;
|
|
return 0;
|
|
}
|
|
nReps++;
|
|
|
|
} while ( nReps < 54 );
|
|
}
|
|
}
|
|
|
|
// Only an issue on the client.
|
|
|
|
if ( pmove->server )
|
|
idx = 0;
|
|
else idx = 1;
|
|
|
|
fTime = pmove->realtime;
|
|
|
|
// Too soon?
|
|
if ( rgStuckCheckTime[player_index()][idx] >= ( fTime - PM_CHECKSTUCK_MINTIME ))
|
|
return 1;
|
|
|
|
rgStuckCheckTime[player_index()][idx] = fTime;
|
|
PM_AddToTouched( trace.pHit, pmove->velocity );
|
|
|
|
i = PM_GetRandomStuckOffsets( player_index(), pmove->server, offset );
|
|
test = base + offset;
|
|
|
|
if(( hitent = TEST_PLAYER( test, (TraceResult *)NULL )) == NULL )
|
|
{
|
|
// ALERT( at_console, "Nudged\n" );
|
|
|
|
PM_ResetStuckOffsets( player_index(), pmove->server );
|
|
|
|
if( i >= 27 ) pmove->origin = test;
|
|
return 0;
|
|
}
|
|
|
|
// If player is flailing while stuck in another player ( should never happen ), then see
|
|
// if we can't "unstick" them forceably.
|
|
if( pmove->cmd.buttons & (IN_JUMP|IN_DUCK|IN_ATTACK) && ( hitent && hitent->v.flags & FL_CLIENT ))
|
|
{
|
|
float x, y, z;
|
|
float xystep = 8.0;
|
|
float zstep = 18.0;
|
|
float xyminmax = xystep;
|
|
float zminmax = 4 * zstep;
|
|
|
|
for ( z = 0; z <= zminmax; z += zstep )
|
|
{
|
|
for ( x = -xyminmax; x <= xyminmax; x += xystep )
|
|
{
|
|
for ( y = -xyminmax; y <= xyminmax; y += xystep )
|
|
{
|
|
test = base;
|
|
test[0] += x;
|
|
test[1] += y;
|
|
test[2] += z;
|
|
|
|
if( TEST_PLAYER( test, (TraceResult *)NULL ) == NULL )
|
|
{
|
|
pmove->origin = test;
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// pmove->origin = base;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
PM_SpectatorMove
|
|
===============
|
|
*/
|
|
void PM_SpectatorMove( void )
|
|
{
|
|
float speed, drop, friction, control, newspeed;
|
|
float currentspeed, addspeed, accelspeed;
|
|
Vector wishvel;
|
|
float fmove, smove;
|
|
Vector wishdir;
|
|
float wishspeed;
|
|
|
|
// this routine keeps track of the spectators position
|
|
// there a two different main move types : track player or moce freely (OBS_ROAMING)
|
|
// doesn't need excate track position, only to generate PVS, so just copy
|
|
// targets position and real view position is calculated on client (saves server CPU)
|
|
|
|
if ( pev->iSpecMode == OBS_ROAMING )
|
|
{
|
|
// Move around in normal spectator method
|
|
speed = pmove->velocity.Length();
|
|
if( speed < 1.0f )
|
|
{
|
|
pmove->velocity = g_vecZero;
|
|
}
|
|
else
|
|
{
|
|
drop = 0;
|
|
friction = pmove->movevars->friction * 1.5f; // extra friction
|
|
control = speed < pmove->movevars->stopspeed ? pmove->movevars->stopspeed : speed;
|
|
drop += control*friction*pmove->frametime;
|
|
|
|
// scale the velocity
|
|
newspeed = speed - drop;
|
|
if( newspeed < 0.0f ) newspeed = 0.0f;
|
|
newspeed /= speed;
|
|
|
|
pmove->velocity *= newspeed;
|
|
}
|
|
|
|
// accelerate
|
|
fmove = pmove->cmd.forwardmove;
|
|
smove = pmove->cmd.sidemove;
|
|
|
|
pmove->forward = pmove->forward.Normalize();
|
|
pmove->right = pmove->right.Normalize();
|
|
|
|
wishvel = pmove->forward * fmove + pmove->right * smove;
|
|
wishvel.z += pmove->cmd.upmove;
|
|
|
|
wishdir = wishvel;
|
|
wishspeed = wishdir.Length();
|
|
wishdir = wishdir.Normalize();
|
|
|
|
// clamp to server defined max speed
|
|
if( wishspeed > pmove->movevars->spectatormaxspeed )
|
|
{
|
|
wishvel *= ( pmove->movevars->spectatormaxspeed / wishspeed );
|
|
wishspeed = pmove->movevars->spectatormaxspeed;
|
|
}
|
|
|
|
currentspeed = DotProduct( pmove->velocity, wishdir );
|
|
addspeed = wishspeed - currentspeed;
|
|
if( addspeed <= 0.0f ) return;
|
|
|
|
accelspeed = pmove->movevars->accelerate * pmove->frametime * wishspeed;
|
|
if( accelspeed > addspeed ) accelspeed = addspeed;
|
|
pmove->velocity += accelspeed * wishdir;
|
|
|
|
// move
|
|
pmove->origin += pmove->velocity * pmove->frametime;
|
|
}
|
|
else
|
|
{
|
|
// all other modes just track some kind of target, so spectator PVS = target PVS
|
|
|
|
// no valid target ?
|
|
if( !pev->aiment || pev->aiment->free )
|
|
return;
|
|
|
|
// use targets position as own origin for PVS
|
|
pmove->angles = pev->aiment->v.angles;
|
|
pmove->origin = pev->aiment->v.origin;
|
|
|
|
// no velocity
|
|
pmove->velocity = g_vecZero;
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
PM_SplineFraction
|
|
|
|
Use for ease-in, ease-out style interpolation (accel/decel)
|
|
Used by ducking code.
|
|
==================
|
|
*/
|
|
float PM_SplineFraction( float value, float scale )
|
|
{
|
|
float valueSquared;
|
|
|
|
value = scale * value;
|
|
valueSquared = value * value;
|
|
|
|
// Nice little ease-in, ease-out spline-like curve
|
|
return 3 * valueSquared - 2 * valueSquared * value;
|
|
}
|
|
|
|
void PM_FixPlayerCrouchStuck( int direction )
|
|
{
|
|
edict_t *hitent;
|
|
int i;
|
|
Vector test;
|
|
|
|
hitent = TEST_PLAYER ( pmove->origin, (TraceResult *)NULL );
|
|
if( hitent == NULL ) return;
|
|
|
|
test = pmove->origin;
|
|
for ( i = 0; i < 36; i++ )
|
|
{
|
|
pmove->origin.z += direction;
|
|
hitent = TEST_PLAYER( pmove->origin, (TraceResult *)NULL );
|
|
if( hitent == (edict_t *)NULL ) return;
|
|
}
|
|
|
|
pmove->origin = test; // Failed
|
|
}
|
|
|
|
void PM_UnDuck( void )
|
|
{
|
|
TraceResult trace;
|
|
Vector newOrigin;
|
|
|
|
newOrigin = pmove->origin;
|
|
|
|
if( pmove->onground != NULL )
|
|
{
|
|
newOrigin += (pmove->player_mins[1] - pmove->player_mins[0]);
|
|
}
|
|
|
|
trace = TRACE_PLAYER( newOrigin, newOrigin, PM_NORMAL );
|
|
|
|
if ( !trace.fStartSolid )
|
|
{
|
|
pmove->usehull = 0;
|
|
|
|
// Oh, no, changing hulls stuck us into something, try unsticking downward first.
|
|
trace = TRACE_PLAYER( newOrigin, newOrigin, PM_NORMAL );
|
|
|
|
if( trace.fStartSolid )
|
|
{
|
|
// See if we are stuck? If so, stay ducked with the duck hull until we have a clear spot
|
|
// ALERT( at_console, "unstick got stuck\n" );
|
|
pmove->usehull = 1;
|
|
return;
|
|
}
|
|
|
|
pev->flags &= ~FL_DUCKING;
|
|
pmove->bInDuck = false;
|
|
pev->view_ofs.z = pmove->player_view[0];
|
|
pev->flDuckTime = 0.0f;
|
|
|
|
pmove->origin = newOrigin;
|
|
|
|
// Recatagorize position since ducking can change origin
|
|
PM_CatagorizePosition();
|
|
}
|
|
}
|
|
|
|
void PM_Duck( void )
|
|
{
|
|
float time;
|
|
float duckFraction;
|
|
int buttonsChanged = ( pev->oldbuttons ^ pmove->cmd.buttons ); // These buttons have changed this frame
|
|
int nButtonPressed = buttonsChanged & pmove->cmd.buttons; // The changed ones still down are "pressed"
|
|
int duckchange = buttonsChanged & IN_DUCK ? 1 : 0;
|
|
int duckpressed = nButtonPressed & IN_DUCK ? 1 : 0;
|
|
|
|
if( pmove->cmd.buttons & IN_DUCK )
|
|
pev->oldbuttons |= IN_DUCK;
|
|
else pev->oldbuttons &= ~IN_DUCK;
|
|
|
|
// Prevent ducking if the iuser3 variable is set
|
|
if( pmove->dead )
|
|
{
|
|
// Try to unduck
|
|
if( pev->flags & FL_DUCKING )
|
|
PM_UnDuck();
|
|
return;
|
|
}
|
|
|
|
if( pev->flags & FL_DUCKING )
|
|
{
|
|
pmove->cmd.forwardmove *= 0.333;
|
|
pmove->cmd.sidemove *= 0.333;
|
|
pmove->cmd.upmove *= 0.333;
|
|
}
|
|
|
|
if(( pmove->cmd.buttons & IN_DUCK ) || ( pmove->bInDuck ) || ( pev->flags & FL_DUCKING ))
|
|
{
|
|
if ( pmove->cmd.buttons & IN_DUCK )
|
|
{
|
|
if ( (nButtonPressed & IN_DUCK ) && !( pev->flags & FL_DUCKING ) )
|
|
{
|
|
// Use 1 second so super long jump will work
|
|
pev->flDuckTime = 1000;
|
|
pmove->bInDuck = true;
|
|
}
|
|
|
|
time = max( 0.0f, ( 1.0f - (float)pev->flDuckTime / 1000.0f ));
|
|
|
|
if( pmove->bInDuck )
|
|
{
|
|
// Finish ducking immediately if duck time is over or not on ground
|
|
if(((float)pev->flDuckTime / 1000.0f <= ( 1.0f - TIME_TO_DUCK )) || ( pmove->onground == NULL ))
|
|
{
|
|
pmove->usehull = 1;
|
|
pev->view_ofs[2] = pmove->player_view[1];
|
|
pev->flags |= FL_DUCKING;
|
|
pmove->bInDuck = false;
|
|
|
|
// HACKHACK - Fudge for collision bug - no time to fix this properly
|
|
if( pmove->onground != NULL )
|
|
{
|
|
pmove->origin -= (pmove->player_mins[1] - pmove->player_mins[0]);
|
|
|
|
// See if we are stuck?
|
|
PM_FixPlayerCrouchStuck( STUCK_MOVEUP );
|
|
|
|
// Recatagorize position since ducking can change origin
|
|
PM_CatagorizePosition();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
float fMore = (VEC_DUCK_HULL_MIN - VEC_HULL_MIN);
|
|
|
|
// Calc parametric time
|
|
duckFraction = PM_SplineFraction( time, (1.0f / TIME_TO_DUCK ));
|
|
pev->view_ofs.z = ((pmove->player_view[1] - fMore) * duckFraction) + (pmove->player_view[0] * (1.0f - duckFraction));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Try to unduck
|
|
PM_UnDuck();
|
|
}
|
|
}
|
|
}
|
|
|
|
void PM_LadderMove( edict_t *pLadder )
|
|
{
|
|
Vector ladderCenter;
|
|
TraceResult trace;
|
|
BOOL onFloor;
|
|
Vector floor;
|
|
Vector modelmins, modelmaxs;
|
|
|
|
if( pev->movetype == MOVETYPE_NOCLIP )
|
|
return;
|
|
|
|
Mod_GetBounds( pLadder->v.modelindex, modelmins, modelmaxs );
|
|
|
|
ladderCenter = (modelmins + modelmaxs) * 0.5f;
|
|
ladderCenter += pLadder->v.origin; // allow for ladders moving around
|
|
pev->movetype = MOVETYPE_FLY;
|
|
|
|
// On ladder, convert movement to be relative to the ladder
|
|
floor = pmove->origin;
|
|
floor.z += pmove->player_mins[pmove->usehull].z - 1;
|
|
|
|
if( POINT_CONTENTS( floor ) == CONTENTS_SOLID )
|
|
onFloor = true;
|
|
else onFloor = false;
|
|
|
|
pev->gravity = 0;
|
|
trace = TRACE_MODEL( pLadder, pmove->origin, ladderCenter );
|
|
|
|
if( trace.flFraction != 1.0f )
|
|
{
|
|
float forward = 0, right = 0;
|
|
Vector vpn, v_right;
|
|
|
|
AngleVectors( pmove->angles, vpn, v_right, (float *)NULL );
|
|
|
|
if( pmove->cmd.buttons & IN_BACK ) forward -= MAX_CLIMB_SPEED;
|
|
if( pmove->cmd.buttons & IN_FORWARD ) forward += MAX_CLIMB_SPEED;
|
|
if( pmove->cmd.buttons & IN_MOVELEFT ) right -= MAX_CLIMB_SPEED;
|
|
if( pmove->cmd.buttons & IN_MOVERIGHT ) right += MAX_CLIMB_SPEED;
|
|
|
|
if( pmove->cmd.buttons & IN_JUMP )
|
|
{
|
|
pev->movetype = MOVETYPE_WALK;
|
|
pmove->velocity = trace.vecPlaneNormal * 270;
|
|
}
|
|
else
|
|
{
|
|
if( forward != 0 || right != 0 )
|
|
{
|
|
Vector velocity, perp, cross, lateral, tmp;
|
|
float normal;
|
|
|
|
// ALERT( at_console, "pev %.2f %.2f %.2f - ", pmove->velocity.x, pmove->velocity.y, pmove->velocity.z );
|
|
// Calculate player's intended velocity
|
|
// Vector velocity = (forward * pmove->forward) + (right * pmove->right);
|
|
velocity = vpn * forward;
|
|
velocity += v_right * right;
|
|
|
|
|
|
// Perpendicular in the ladder plane
|
|
perp = CrossProduct( Vector( 0, 0, 1 ), trace.vecPlaneNormal );
|
|
perp = perp.Normalize();
|
|
|
|
// decompose velocity into ladder plane
|
|
normal = DotProduct( velocity, trace.vecPlaneNormal );
|
|
// This is the velocity into the face of the ladder
|
|
cross = trace.vecPlaneNormal * normal;
|
|
|
|
// This is the player's additional velocity
|
|
lateral = velocity - cross;
|
|
|
|
// This turns the velocity into the face of the ladder into velocity that
|
|
// is roughly vertically perpendicular to the face of the ladder.
|
|
// NOTE: It IS possible to face up and move down or face down and move up
|
|
// because the velocity is a sum of the directional velocity and the converted
|
|
// velocity through the face of the ladder -- by design.
|
|
tmp = CrossProduct( trace.vecPlaneNormal, perp );
|
|
pmove->velocity = lateral + (tmp * -normal);
|
|
|
|
if( onFloor && normal > 0.0f ) // On ground moving away from the ladder
|
|
{
|
|
pmove->velocity += trace.vecPlaneNormal * MAX_CLIMB_SPEED;
|
|
}
|
|
// pmove->velocity = lateral - (CrossProduct( trace.vecPlaneNormal, perp ) * normal);
|
|
}
|
|
else pmove->velocity = g_vecZero;
|
|
}
|
|
}
|
|
}
|
|
|
|
// moved here from engine
|
|
void PM_HullForBsp( edict_t *pe, Vector &test )
|
|
{
|
|
if( Mod_GetType( pe->v.modelindex ) == mod_brush )
|
|
{
|
|
Vector hull_mins;
|
|
|
|
if( pe->v.size.x < 3 )
|
|
hull_mins = pmove->player_mins[2];
|
|
else if( pe->v.size.x <= 32 )
|
|
hull_mins = pmove->player_mins[1];
|
|
else hull_mins = pmove->player_mins[3];
|
|
|
|
test = hull_mins + pe->v.origin;
|
|
}
|
|
else
|
|
{
|
|
// studiomodel ladder
|
|
test = pe->v.origin;
|
|
}
|
|
}
|
|
|
|
edict_t *PM_Ladder( void )
|
|
{
|
|
edict_t *pe;
|
|
Vector test;
|
|
int i;
|
|
|
|
for( i = 0; i < pmove->numladders; i++ )
|
|
{
|
|
pe = pmove->ladders[i];
|
|
|
|
if( Mod_GetType( pe->v.modelindex ) == mod_brush && pe->v.skin == CONTENTS_LADDER )
|
|
{
|
|
PM_HullForBsp( pe, test );
|
|
|
|
// Offset the test point appropriately for this hull.
|
|
test = pmove->origin - test;
|
|
|
|
// Test the player's hull for intersection with this model
|
|
if( POINT_CONTENTS( test ) == CONTENTS_EMPTY )
|
|
continue;
|
|
|
|
return pe;
|
|
}
|
|
}
|
|
return (edict_t *)NULL;
|
|
}
|
|
|
|
void PM_WaterJump( void )
|
|
{
|
|
if( pmove->flWaterJumpTime > 10000.0f )
|
|
pmove->flWaterJumpTime = 10000.0f;
|
|
|
|
if( !pmove->flWaterJumpTime )
|
|
return;
|
|
|
|
pmove->flWaterJumpTime -= pmove->cmd.msec;
|
|
if ( pmove->flWaterJumpTime < 0 || !pev->waterlevel )
|
|
{
|
|
pmove->flWaterJumpTime = 0;
|
|
pev->flags &= ~FL_WATERJUMP;
|
|
}
|
|
|
|
pmove->velocity.x = pmove->movedir.x;
|
|
pmove->velocity.y = pmove->movedir.y;
|
|
}
|
|
|
|
/*
|
|
============
|
|
PM_AddGravity
|
|
|
|
============
|
|
*/
|
|
void PM_AddGravity( void )
|
|
{
|
|
float ent_gravity;
|
|
|
|
if( pev->gravity )
|
|
ent_gravity = pev->gravity;
|
|
else ent_gravity = 1.0f;
|
|
|
|
// Add gravity incorrectly
|
|
pmove->velocity.z -= (ent_gravity * pmove->movevars->gravity * pmove->frametime );
|
|
pmove->velocity.z += pmove->basevelocity.z * pmove->frametime;
|
|
pmove->basevelocity.z = 0.0f;
|
|
|
|
PM_CheckVelocity();
|
|
}
|
|
|
|
/*
|
|
============
|
|
PM_PushEntity
|
|
|
|
Does not change the entities velocity at all
|
|
============
|
|
*/
|
|
TraceResult PM_PushEntity( Vector push )
|
|
{
|
|
TraceResult trace;
|
|
Vector end;
|
|
|
|
end = pmove->origin + push;
|
|
trace = TRACE_PLAYER( pmove->origin, end, PM_NORMAL );
|
|
pmove->origin = trace.vecEndPos;
|
|
|
|
// So we can run impact function afterwards.
|
|
if( trace.flFraction < 1.0f && !trace.fAllSolid )
|
|
PM_AddToTouched( trace.pHit, pmove->velocity );
|
|
return trace;
|
|
}
|
|
|
|
/*
|
|
============
|
|
PM_Physics_Toss
|
|
|
|
Dead player flying through air., e.g.
|
|
============
|
|
*/
|
|
void PM_Physics_Toss( void )
|
|
{
|
|
TraceResult trace;
|
|
Vector move;
|
|
float backoff;
|
|
|
|
PM_CheckWater();
|
|
|
|
if( pmove->velocity.z > 0 )
|
|
pmove->onground = (edict_t *)NULL;
|
|
|
|
// If on ground and not moving, return.
|
|
if( pmove->onground != NULL )
|
|
{
|
|
if( pmove->basevelocity == g_vecZero && pmove->velocity == g_vecZero )
|
|
return;
|
|
}
|
|
|
|
PM_CheckVelocity ();
|
|
|
|
// add gravity
|
|
if( pev->movetype != MOVETYPE_FLY && pev->movetype != MOVETYPE_BOUNCEMISSILE && pev->movetype != MOVETYPE_FLYMISSILE )
|
|
PM_AddGravity ();
|
|
|
|
// move origin
|
|
// Base velocity is not properly accounted for since this entity will move again
|
|
// after the bounce without taking it into account
|
|
pmove->velocity += pmove->basevelocity;
|
|
|
|
PM_CheckVelocity();
|
|
move = pmove->velocity * pmove->frametime;
|
|
pmove->velocity = pmove->basevelocity - pmove->velocity;
|
|
|
|
trace = PM_PushEntity( move ); // Should this clear basevelocity
|
|
|
|
PM_CheckVelocity();
|
|
|
|
if( trace.fAllSolid )
|
|
{
|
|
// entity is trapped in another solid
|
|
pmove->onground = trace.pHit;
|
|
pmove->velocity = g_vecZero;
|
|
return;
|
|
}
|
|
|
|
if( trace.flFraction == 1.0f )
|
|
{
|
|
PM_CheckWater();
|
|
return;
|
|
}
|
|
|
|
if( pev->movetype == MOVETYPE_BOUNCE )
|
|
backoff = 2.0f - pev->friction;
|
|
else if( pev->movetype == MOVETYPE_BOUNCEMISSILE )
|
|
backoff = 2.0f;
|
|
else backoff = 1.0f;
|
|
|
|
PM_ClipVelocity( pmove->velocity, trace.vecPlaneNormal, pmove->velocity, backoff );
|
|
|
|
// stop if on ground
|
|
if( trace.vecPlaneNormal.z > 0.7f )
|
|
{
|
|
float vel;
|
|
Vector base;
|
|
|
|
base = g_vecZero;
|
|
if( pmove->velocity.z < pmove->movevars->gravity * pmove->frametime )
|
|
{
|
|
// we're rolling on the ground, add static friction.
|
|
pmove->onground = trace.pHit;
|
|
pmove->velocity.z = 0.0f;
|
|
}
|
|
|
|
vel = DotProduct( pmove->velocity, pmove->velocity );
|
|
|
|
// ALERT( at_console, "%f %f: %.0f %.0f %.0f\n", vel, trace.flFraction, pmove->velocity.x, pmove->velocity.y, pmove->velocity.z );
|
|
|
|
if( vel < 900 || ( pev->movetype != MOVETYPE_BOUNCE && pev->movetype != MOVETYPE_BOUNCEMISSILE ))
|
|
{
|
|
pmove->onground = trace.pHit;
|
|
pmove->velocity = g_vecZero;
|
|
}
|
|
else
|
|
{
|
|
move = pmove->velocity * ((1.0f - trace.flFraction) * pmove->frametime * 0.9f);
|
|
trace = PM_PushEntity (move);
|
|
}
|
|
pmove->velocity -= base;
|
|
}
|
|
|
|
// check for in water
|
|
PM_CheckWater();
|
|
}
|
|
|
|
/*
|
|
====================
|
|
PM_NoClip
|
|
|
|
====================
|
|
*/
|
|
void PM_NoClip( void )
|
|
{
|
|
Vector wishvel;
|
|
float fmove, smove;
|
|
|
|
// Copy movement amounts
|
|
fmove = pmove->cmd.forwardmove;
|
|
smove = pmove->cmd.sidemove;
|
|
|
|
pmove->forward = pmove->forward.Normalize();
|
|
pmove->right = pmove->right.Normalize();
|
|
|
|
// Determine x and y parts of velocity
|
|
wishvel = pmove->forward * fmove + pmove->right * smove;
|
|
wishvel.z += pmove->cmd.upmove;
|
|
|
|
pmove->origin += wishvel * pmove->frametime;
|
|
|
|
// Zero out the velocity so that we don't accumulate a huge downward velocity from
|
|
// gravity, etc.
|
|
pmove->velocity = g_vecZero;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Corrects bunny jumping ( where player initiates a bunny jump before other
|
|
// movement logic runs, thus making onground == -1 thus making PM_Friction get skipped and
|
|
// running PM_AirMove, which doesn't crop velocity to maxspeed like the ground / other
|
|
// movement logic does.
|
|
//-----------------------------------------------------------------------------
|
|
void PM_PreventMegaBunnyJumping( void )
|
|
{
|
|
float spd; // Current player speed
|
|
float fraction; // If we have to crop, apply this cropping fraction to velocity
|
|
float maxscaledspeed; // Speed at which bunny jumping is limited
|
|
|
|
maxscaledspeed = BUNNYJUMP_MAX_SPEED_FACTOR * pmove->maxspeed;
|
|
|
|
// Don't divide by zero
|
|
if( maxscaledspeed <= 0.0f )
|
|
return;
|
|
|
|
spd = pmove->velocity.Length();
|
|
|
|
if( spd <= maxscaledspeed )
|
|
return;
|
|
|
|
fraction = ( maxscaledspeed / spd ) * 0.65f; // Returns the modifier for the velocity
|
|
pmove->velocity *= fraction; // Crop it down!.
|
|
}
|
|
|
|
/*
|
|
=============
|
|
PM_Jump
|
|
=============
|
|
*/
|
|
void PM_Jump( void )
|
|
{
|
|
if( pmove->dead )
|
|
{
|
|
pev->oldbuttons |= IN_JUMP; // don't jump again until released
|
|
return;
|
|
}
|
|
|
|
BOOL tfc = atoi( Info_ValueForKey( "tfc" )) == 1 ? true : false;
|
|
|
|
// Spy that's feigning death cannot jump
|
|
if( tfc && ( pev->deadflag == ( DEAD_DISCARDBODY + 1 )))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// See if we are waterjumping. If so, decrement count and return.
|
|
if( pmove->flWaterJumpTime )
|
|
{
|
|
pmove->flWaterJumpTime -= pmove->cmd.msec;
|
|
if( pmove->flWaterJumpTime < 0.0f )
|
|
pmove->flWaterJumpTime = 0.0f;
|
|
return;
|
|
}
|
|
|
|
// If we are in the water most of the way...
|
|
if( pev->waterlevel >= 2 )
|
|
{
|
|
// swimming, not jumping
|
|
pmove->onground = (edict_t *)NULL;
|
|
|
|
if( pev->watertype == CONTENTS_WATER ) // We move up a certain amount
|
|
pmove->velocity.z = 100;
|
|
else if( pev->watertype == CONTENTS_SLIME )
|
|
pmove->velocity.z = 80;
|
|
else pmove->velocity.z = 50; // LAVA
|
|
|
|
// play swiming sound
|
|
if( pev->flSwimTime <= 0.0f )
|
|
{
|
|
// Don't play sound again for 1 second
|
|
pev->flSwimTime = 1000;
|
|
|
|
switch( RANDOM_LONG( 0, 3 ))
|
|
{
|
|
case 0:
|
|
EmitSound( CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM, PITCH_NORM );
|
|
break;
|
|
case 1:
|
|
EmitSound( CHAN_BODY, "player/pl_wade2.wav", 1, ATTN_NORM, PITCH_NORM );
|
|
break;
|
|
case 2:
|
|
EmitSound( CHAN_BODY, "player/pl_wade3.wav", 1, ATTN_NORM, PITCH_NORM );
|
|
break;
|
|
case 3:
|
|
EmitSound( CHAN_BODY, "player/pl_wade4.wav", 1, ATTN_NORM, PITCH_NORM );
|
|
break;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// No more effect
|
|
if( pmove->onground == NULL )
|
|
{
|
|
// Flag that we jumped.
|
|
// HACK HACK HACK
|
|
// Remove this when the game.dll no longer does physics code!!!!
|
|
pev->oldbuttons |= IN_JUMP; // don't jump again until released
|
|
return; // in air, so no effect
|
|
}
|
|
|
|
if( pev->oldbuttons & IN_JUMP )
|
|
return; // don't pogo stick
|
|
|
|
// In the air now.
|
|
pmove->onground = (edict_t *)NULL;
|
|
|
|
PM_PreventMegaBunnyJumping();
|
|
|
|
if( tfc ) EmitSound( CHAN_BODY, "player/plyrjmp8.wav", 0.5, ATTN_NORM, PITCH_NORM );
|
|
else PM_PlayStepSound( PM_MapTextureTypeStepType( pmove->chTextureType ), 1.0 );
|
|
|
|
// See if user can super long jump?
|
|
BOOL cansuperjump = atoi( Info_ValueForKey( "slj" )) == 1 ? true : false;
|
|
|
|
// Acclerate upward
|
|
// If we are ducking...
|
|
if(( pmove->bInDuck ) || ( pev->flags & FL_DUCKING ))
|
|
{
|
|
// Adjust for super long jump module
|
|
// UNDONE -- note this should be based on forward angles, not current velocity.
|
|
if( cansuperjump && ( pmove->cmd.buttons & IN_DUCK ) && ( pev->flDuckTime > 0.0f ) && pmove->velocity.Length() > 50.0f )
|
|
{
|
|
pev->punchangle.x = -5.0f;
|
|
pmove->velocity.x = pmove->forward.x * PLAYER_LONGJUMP_SPEED * 1.6f;
|
|
pmove->velocity.y = pmove->forward.y * PLAYER_LONGJUMP_SPEED * 1.6f;
|
|
pmove->velocity.z = sqrt( 2 * 800 * 56.0f );
|
|
}
|
|
else pmove->velocity.z = sqrt( 2 * 800 * 45.0f );
|
|
}
|
|
else pmove->velocity.z = sqrt( 2 * 800 * 45.0f );
|
|
|
|
// Decay it for simulation
|
|
PM_FixupGravityVelocity();
|
|
|
|
// Flag that we jumped.
|
|
pev->oldbuttons |= IN_JUMP; // don't jump again until released
|
|
}
|
|
|
|
/*
|
|
=============
|
|
PM_CheckWaterJump
|
|
=============
|
|
*/
|
|
void PM_CheckWaterJump( void )
|
|
{
|
|
Vector vecStart, vecEnd;
|
|
Vector flatforward;
|
|
Vector flatvelocity;
|
|
float curspeed;
|
|
int savehull;
|
|
TraceResult tr;
|
|
|
|
// Already water jumping.
|
|
if( pmove->flWaterJumpTime )
|
|
return;
|
|
|
|
// Don't hop out if we just jumped in
|
|
if( pmove->velocity.z < -180 )
|
|
return; // only hop out if we are moving up
|
|
|
|
// See if we are backing up
|
|
flatvelocity.x = pmove->velocity.x;
|
|
flatvelocity.y = pmove->velocity.y;
|
|
flatvelocity.z = 0.0f;
|
|
|
|
// Must be moving
|
|
curspeed = flatvelocity.Length();
|
|
flatvelocity = flatvelocity.Normalize();
|
|
|
|
// see if near an edge
|
|
flatforward.x = pmove->forward.x;
|
|
flatforward.y = pmove->forward.y;
|
|
flatforward.z = 0.0f;
|
|
flatforward = flatforward.Normalize();
|
|
|
|
// Are we backing into water from steps or something? If so, don't pop forward
|
|
if( curspeed != 0.0 && ( DotProduct( flatvelocity, flatforward ) < 0.0f ))
|
|
return;
|
|
|
|
vecStart = pmove->origin;
|
|
vecStart.z += WJ_HEIGHT;
|
|
|
|
vecEnd = vecStart + flatforward * 24;
|
|
|
|
// Trace, this trace should use the point sized collision hull
|
|
savehull = pmove->usehull;
|
|
pmove->usehull = 2;
|
|
tr = TRACE_PLAYER( vecStart, vecEnd, PM_NORMAL );
|
|
|
|
if( tr.flFraction < 1.0 && fabs( tr.vecPlaneNormal.z ) < 0.1f ) // Facing a near vertical wall?
|
|
{
|
|
vecStart.z += pmove->player_maxs[savehull].z - WJ_HEIGHT;
|
|
vecEnd = vecStart + flatforward * 24;
|
|
pmove->movedir = tr.vecPlaneNormal * -50;
|
|
|
|
tr = TRACE_PLAYER( vecStart, vecEnd, PM_NORMAL );
|
|
if( tr.flFraction == 1.0f )
|
|
{
|
|
pmove->flWaterJumpTime = 2000;
|
|
pmove->velocity.z = 225;
|
|
pev->oldbuttons |= IN_JUMP;
|
|
pev->flags |= FL_WATERJUMP;
|
|
}
|
|
}
|
|
// Reset the collision hull
|
|
pmove->usehull = savehull;
|
|
}
|
|
|
|
void PM_CheckFalling( void )
|
|
{
|
|
if( pmove->onground != NULL && !pmove->dead && pev->flFallVelocity >= PLAYER_FALL_PUNCH_THRESHHOLD )
|
|
{
|
|
float fvol = 0.5;
|
|
|
|
if( pev->flJumpPadTime && pev->flJumpPadTime < pmove->realtime )
|
|
{
|
|
// scale delta if was pushed by jump pad
|
|
float delta = (1.0f + pmove->realtime - pev->flJumpPadTime) * 0.5f;
|
|
pev->flFallVelocity /= delta;
|
|
}
|
|
|
|
if( pev->waterlevel > 0 )
|
|
{
|
|
// does nothing
|
|
}
|
|
else if( pev->flFallVelocity > PLAYER_MAX_SAFE_FALL_SPEED )
|
|
{
|
|
// NOTE: In the original game dll, there were no breaks after these cases, causing the first one to
|
|
// cascade into the second
|
|
switch ( RANDOM_LONG( 0, 1 ))
|
|
{
|
|
case 0:
|
|
EmitSound( CHAN_VOICE, "player/pl_fallpain2.wav", 1, ATTN_NORM, PITCH_NORM );
|
|
break;
|
|
case 1:
|
|
EmitSound( CHAN_VOICE, "player/pl_fallpain3.wav", 1, ATTN_NORM, PITCH_NORM );
|
|
break;
|
|
}
|
|
fvol = 1.0f;
|
|
}
|
|
else if( pev->flFallVelocity > PLAYER_MAX_SAFE_FALL_SPEED / 2.0f )
|
|
{
|
|
if( atoi( Info_ValueForKey( "tfc" )))
|
|
{
|
|
EmitSound( CHAN_VOICE, "player/pl_fallpain3.wav", 1, ATTN_NORM, PITCH_NORM );
|
|
}
|
|
fvol = 0.85f;
|
|
}
|
|
else if( pev->flFallVelocity < PLAYER_MIN_BOUNCE_SPEED )
|
|
{
|
|
fvol = 0.0f;
|
|
}
|
|
|
|
if( fvol > 0.0f )
|
|
{
|
|
// Play landing step right away
|
|
pev->flTimeStepSound = 0;
|
|
|
|
PM_UpdateStepSound();
|
|
|
|
// play step sound for current texture
|
|
PM_PlayStepSound( PM_MapTextureTypeStepType( pmove->chTextureType ), fvol );
|
|
|
|
// Knock the screen around a little bit, temporary effect
|
|
pev->punchangle.z = pev->flFallVelocity * 0.013f; // punch z axis
|
|
|
|
if( pev->punchangle.x > 8.0f ) pev->punchangle.x = 8.0f;
|
|
}
|
|
}
|
|
|
|
if( pmove->onground != NULL ) pev->flFallVelocity = 0.0f;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PM_PlayWaterSounds
|
|
|
|
=================
|
|
*/
|
|
void PM_PlayWaterSounds( void )
|
|
{
|
|
// Did we enter or leave water?
|
|
if(( pmove->oldwaterlevel == 0 && pev->waterlevel != 0 ) || ( pmove->oldwaterlevel != 0 && pev->waterlevel == 0 ))
|
|
{
|
|
switch( RANDOM_LONG( 0, 3 ))
|
|
{
|
|
case 0:
|
|
EmitSound( CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM, PITCH_NORM );
|
|
break;
|
|
case 1:
|
|
EmitSound( CHAN_BODY, "player/pl_wade2.wav", 1, ATTN_NORM, PITCH_NORM );
|
|
break;
|
|
case 2:
|
|
EmitSound( CHAN_BODY, "player/pl_wade3.wav", 1, ATTN_NORM, PITCH_NORM );
|
|
break;
|
|
case 3:
|
|
EmitSound( CHAN_BODY, "player/pl_wade4.wav", 1, ATTN_NORM, PITCH_NORM );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
PM_CalcRoll
|
|
|
|
===============
|
|
*/
|
|
float PM_CalcRoll( Vector angles, Vector velocity, float rollangle, float rollspeed )
|
|
{
|
|
Vector forward, right, up;
|
|
|
|
AngleVectors( angles, forward, right, up );
|
|
|
|
float side = DotProduct( velocity, right );
|
|
float sign = side < 0 ? -1 : 1;
|
|
side = fabs( side );
|
|
float value = rollangle;
|
|
|
|
if( side < rollspeed )
|
|
{
|
|
side = side * value / rollspeed;
|
|
}
|
|
else
|
|
{
|
|
side = value;
|
|
}
|
|
return side * sign;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
PM_DropPunchAngle
|
|
|
|
=============
|
|
*/
|
|
void PM_DropPunchAngle( Vector &punchangle )
|
|
{
|
|
float len = punchangle.Length();
|
|
|
|
punchangle = punchangle.Normalize();
|
|
len -= (10.0f + len * 0.5f) * pmove->frametime;
|
|
len = max( len, 0.0f );
|
|
punchangle *= len;
|
|
}
|
|
|
|
/*
|
|
==============
|
|
PM_CheckParamters
|
|
|
|
==============
|
|
*/
|
|
void PM_CheckParamters( void )
|
|
{
|
|
float spd = 0;
|
|
float maxspeed;
|
|
Vector v_angle;
|
|
|
|
spd += ( pmove->cmd.forwardmove * pmove->cmd.forwardmove );
|
|
spd += ( pmove->cmd.sidemove * pmove->cmd.sidemove );
|
|
spd += ( pmove->cmd.upmove * pmove->cmd.upmove );
|
|
spd = sqrt( spd );
|
|
|
|
maxspeed = pmove->clientmaxspeed; //atof( Info_ValueForKey( "maxspd" ));
|
|
if( maxspeed != 0.0f ) pmove->maxspeed = min( maxspeed, pmove->maxspeed );
|
|
|
|
if(( spd != 0.0f ) && ( spd > pmove->maxspeed ))
|
|
{
|
|
float fRatio = pmove->maxspeed / spd;
|
|
|
|
pmove->cmd.forwardmove *= fRatio;
|
|
pmove->cmd.sidemove *= fRatio;
|
|
pmove->cmd.upmove *= fRatio;
|
|
}
|
|
|
|
if ( pev->flags & FL_FROZEN || pev->flags & FL_ONTRAIN || pmove->dead )
|
|
{
|
|
pmove->cmd.forwardmove = 0;
|
|
pmove->cmd.sidemove = 0;
|
|
pmove->cmd.upmove = 0;
|
|
|
|
if(!( pev->flags & FL_ONTRAIN ))
|
|
pmove->cmd.buttons = 0;
|
|
}
|
|
|
|
PM_DropPunchAngle( pev->punchangle );
|
|
|
|
// Take angles from command.
|
|
if( !pmove->dead )
|
|
{
|
|
v_angle = pmove->cmd.viewangles + pev->punchangle;
|
|
|
|
// Set up view angles.
|
|
pmove->angles[ROLL] = PM_CalcRoll( v_angle, pmove->velocity, pmove->movevars->rollangle, pmove->movevars->rollspeed );
|
|
pmove->angles[PITCH] = v_angle[PITCH];
|
|
pmove->angles[YAW] = v_angle[YAW];
|
|
}
|
|
else pmove->angles = pmove->oldangles;
|
|
|
|
// Set dead player view_offset
|
|
if( pmove->dead ) pev->view_ofs.z = PM_DEAD_VIEWHEIGHT;
|
|
|
|
// Adjust client view angles to match values used on server.
|
|
if( pmove->angles[YAW] > 180.0f ) pmove->angles[YAW] -= 360.0f;
|
|
}
|
|
|
|
void PM_ReduceTimers( void )
|
|
{
|
|
if( pev->flTimeStepSound > 0.0f )
|
|
{
|
|
pev->flTimeStepSound -= pmove->cmd.msec;
|
|
if( pev->flTimeStepSound < 0.0f )
|
|
pev->flTimeStepSound = 0.0f;
|
|
}
|
|
if( pev->flDuckTime > 0.0f )
|
|
{
|
|
pev->flDuckTime -= pmove->cmd.msec;
|
|
if( pev->flDuckTime < 0.0f )
|
|
pev->flDuckTime = 0.0f;
|
|
}
|
|
if( pev->flSwimTime > 0.0f )
|
|
{
|
|
pev->flSwimTime -= pmove->cmd.msec;
|
|
if( pev->flSwimTime < 0.0f )
|
|
pev->flSwimTime = 0.0f;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
PlayerMove
|
|
|
|
Returns with origin, angles, and velocity modified in place.
|
|
|
|
Numtouch and touchindex[] will be set if any of the physents
|
|
were contacted during the move.
|
|
=============
|
|
*/
|
|
void PM_PlayerMove( BOOL server )
|
|
{
|
|
// Are we running server code?
|
|
pmove->server = server;
|
|
|
|
// Adjust speeds etc.
|
|
PM_CheckParamters();
|
|
|
|
// Assume we don't touch anything
|
|
pmove->numtouch = 0;
|
|
pmove->dead = (pev->health <= 0.0f) ? true : false;
|
|
|
|
// # of msec to apply movement
|
|
pmove->frametime = pmove->cmd.msec * 0.001f;
|
|
|
|
PM_ReduceTimers();
|
|
|
|
// Convert view angles to vectors
|
|
AngleVectors( pmove->angles, pmove->forward, pmove->right, pmove->up );
|
|
|
|
// PM_ShowClipBox();
|
|
|
|
// Special handling for spectator and observers. (iuser1 is set if the player's in observer mode)
|
|
if(( pev->flags & FL_SPECTATOR ) || pev->iSpecMode > 0 )
|
|
{
|
|
PM_SpectatorMove();
|
|
PM_CatagorizePosition();
|
|
return;
|
|
}
|
|
|
|
// Always try and unstick us unless we are in NOCLIP mode
|
|
if( pev->movetype != MOVETYPE_NOCLIP && pev->movetype != MOVETYPE_NONE )
|
|
{
|
|
if( PM_CheckStuck( ))
|
|
{
|
|
return; // Can't move, we're stuck
|
|
}
|
|
}
|
|
|
|
// Now that we are "unstuck", see where we are ( waterlevel and type, pmove->onground ).
|
|
PM_CatagorizePosition ();
|
|
|
|
// Store off the starting water level
|
|
pmove->oldwaterlevel = pev->waterlevel;
|
|
|
|
// If we are not on ground, store off how fast we are moving down
|
|
if( pmove->onground == NULL ) pev->flFallVelocity = -pmove->velocity.z;
|
|
|
|
edict_t *pLadder = (edict_t *)NULL;
|
|
|
|
g_onladder = false;
|
|
|
|
// Don't run ladder code if dead or on a train
|
|
if( !pmove->dead && !( pev->flags & FL_ONTRAIN ))
|
|
{
|
|
pLadder = PM_Ladder();
|
|
if( pLadder )
|
|
{
|
|
g_onladder = true;
|
|
}
|
|
}
|
|
|
|
PM_UpdateStepSound ();
|
|
PM_Duck ();
|
|
|
|
// Don't run ladder code if dead or on a train
|
|
if( !pmove->dead && !( pev->flags & FL_ONTRAIN ))
|
|
{
|
|
if( pLadder )
|
|
{
|
|
PM_LadderMove( pLadder );
|
|
}
|
|
else if( pev->movetype != MOVETYPE_WALK && pev->movetype != MOVETYPE_NOCLIP )
|
|
{
|
|
// Clear ladder stuff unless player is noclipping
|
|
// it will be set immediately again next frame if necessary
|
|
pev->movetype = MOVETYPE_WALK;
|
|
}
|
|
}
|
|
|
|
// Slow down, I'm pulling it! (a box maybe) but only when I'm standing on ground
|
|
if(( pmove->onground != NULL ) && ( pmove->cmd.buttons & IN_USE ))
|
|
{
|
|
// FIXME: check for really pulling!!!
|
|
pmove->velocity *= 0.3f;
|
|
}
|
|
|
|
// Handle movement
|
|
switch( pev->movetype )
|
|
{
|
|
default:
|
|
ALERT( at_error, "Bogus pmove player movetype %i on %s\n", pev->movetype, pmove->server ? "server" : "client" );
|
|
break;
|
|
case MOVETYPE_NONE:
|
|
break;
|
|
case MOVETYPE_NOCLIP:
|
|
PM_NoClip();
|
|
break;
|
|
case MOVETYPE_TOSS:
|
|
case MOVETYPE_BOUNCE:
|
|
PM_Physics_Toss();
|
|
break;
|
|
case MOVETYPE_FLY:
|
|
PM_CheckWater();
|
|
|
|
// Was jump button pressed?
|
|
// If so, set velocity to 270 away from ladder. This is currently wrong.
|
|
// Also, set MOVE_TYPE to walk, too.
|
|
if( pmove->cmd.buttons & IN_JUMP )
|
|
{
|
|
if( !pLadder ) PM_Jump();
|
|
}
|
|
else
|
|
{
|
|
pev->oldbuttons &= ~IN_JUMP;
|
|
}
|
|
|
|
// Perform the move accounting for any base velocity.
|
|
pmove->velocity += pmove->basevelocity;
|
|
PM_FlyMove ();
|
|
pmove->velocity -= pmove->basevelocity;
|
|
break;
|
|
case MOVETYPE_WALK:
|
|
if( !PM_InWater( )) PM_AddCorrectGravity();
|
|
|
|
// If we are leaping out of the water, just update the counters.
|
|
if( pmove->flWaterJumpTime )
|
|
{
|
|
PM_WaterJump();
|
|
PM_FlyMove();
|
|
|
|
// Make sure waterlevel is set correctly
|
|
PM_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
|
|
if( pev->waterlevel >= 2 )
|
|
{
|
|
if( pev->waterlevel == 2 )
|
|
{
|
|
PM_CheckWaterJump();
|
|
}
|
|
|
|
// If we are falling again, then we must not trying to jump out of water any more.
|
|
if( pmove->velocity.z < 0 && pmove->flWaterJumpTime )
|
|
{
|
|
pmove->flWaterJumpTime = 0;
|
|
}
|
|
|
|
// Was jump button pressed?
|
|
if( pmove->cmd.buttons & IN_JUMP )
|
|
{
|
|
PM_Jump ();
|
|
}
|
|
else
|
|
{
|
|
pev->oldbuttons &= ~IN_JUMP;
|
|
}
|
|
|
|
// Perform regular water movement
|
|
PM_WaterMove();
|
|
|
|
pmove->velocity -= pmove->basevelocity;
|
|
|
|
// Get a final position
|
|
PM_CatagorizePosition();
|
|
}
|
|
else // Not underwater
|
|
{
|
|
// Was jump button pressed?
|
|
if( pmove->cmd.buttons & IN_JUMP )
|
|
{
|
|
if( !pLadder )
|
|
{
|
|
PM_Jump ();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pev->oldbuttons &= ~IN_JUMP;
|
|
}
|
|
|
|
// Fricion is handled before we add in any base velocity. That way, if we are on a conveyor,
|
|
// we don't slow when standing still, relative to the conveyor.
|
|
if( pmove->onground != NULL )
|
|
{
|
|
pmove->velocity.z = 0.0f;
|
|
PM_Friction();
|
|
}
|
|
|
|
// Make sure velocity is valid.
|
|
PM_CheckVelocity();
|
|
|
|
// Are we on ground now
|
|
if( pmove->onground != NULL )
|
|
{
|
|
PM_WalkMove();
|
|
}
|
|
else
|
|
{
|
|
PM_AirMove(); // Take into account movement when in air.
|
|
}
|
|
|
|
// Set final flags.
|
|
PM_CatagorizePosition();
|
|
|
|
// 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?)
|
|
pmove->velocity -= pmove->basevelocity;
|
|
|
|
// Make sure velocity is valid.
|
|
PM_CheckVelocity();
|
|
|
|
// Add any remaining gravitational component.
|
|
if( !PM_InWater( ))
|
|
{
|
|
PM_FixupGravityVelocity();
|
|
}
|
|
|
|
// If we are on ground, no downward velocity.
|
|
if( pmove->onground != NULL )
|
|
{
|
|
pmove->velocity.z = 0.0f;
|
|
}
|
|
|
|
// See if we landed on the ground with enough force to play
|
|
// a landing sound.
|
|
PM_CheckFalling();
|
|
}
|
|
|
|
// Did we enter or leave the water?
|
|
PM_PlayWaterSounds();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void PM_CreateStuckTable( void )
|
|
{
|
|
float x, y, z;
|
|
int idx = 0;
|
|
int i;
|
|
Vector zi;
|
|
|
|
memset( rgv3tStuckTable, 0, 54 * sizeof( Vector ));
|
|
|
|
// Little Moves.
|
|
x = y = 0.0f;
|
|
|
|
// Z moves
|
|
for( z = -0.125f; z <= 0.125f; z += 0.125f )
|
|
{
|
|
rgv3tStuckTable[idx].x = x;
|
|
rgv3tStuckTable[idx].y = y;
|
|
rgv3tStuckTable[idx].z = z;
|
|
idx++;
|
|
}
|
|
|
|
x = z = 0.0f;
|
|
// Y moves
|
|
for( y = -0.125f; y <= 0.125f; y += 0.125f )
|
|
{
|
|
rgv3tStuckTable[idx].x = x;
|
|
rgv3tStuckTable[idx].y = y;
|
|
rgv3tStuckTable[idx].z = z;
|
|
idx++;
|
|
}
|
|
|
|
y = z = 0.0f;
|
|
// X moves
|
|
for( x = -0.125f; x <= 0.125f; x += 0.125f )
|
|
{
|
|
rgv3tStuckTable[idx].x = x;
|
|
rgv3tStuckTable[idx].y = y;
|
|
rgv3tStuckTable[idx].z = z;
|
|
idx++;
|
|
}
|
|
|
|
// Remaining multi axis nudges.
|
|
for( x = -0.125f; x <= 0.125f; x += 0.250f )
|
|
{
|
|
for( y = - 0.125f; y <= 0.125f; y += 0.250f )
|
|
{
|
|
for( z = -0.125f; z <= 0.125f; z += 0.250f )
|
|
{
|
|
rgv3tStuckTable[idx].x = x;
|
|
rgv3tStuckTable[idx].y = y;
|
|
rgv3tStuckTable[idx].z = z;
|
|
idx++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Big Moves.
|
|
x = y = 0.0f;
|
|
zi[0] = 0.0f;
|
|
zi[1] = 1.0f;
|
|
zi[2] = 6.0f;
|
|
|
|
for( i = 0; i < 3; i++ )
|
|
{
|
|
// Z moves
|
|
z = zi[i];
|
|
rgv3tStuckTable[idx].x = x;
|
|
rgv3tStuckTable[idx].y = y;
|
|
rgv3tStuckTable[idx].z = z;
|
|
idx++;
|
|
}
|
|
|
|
x = z = 0.0f;
|
|
// Y moves
|
|
for( y = -2.0f; y <= 2.0f; y += 2.0f )
|
|
{
|
|
rgv3tStuckTable[idx].x = x;
|
|
rgv3tStuckTable[idx].y = y;
|
|
rgv3tStuckTable[idx].z = z;
|
|
idx++;
|
|
}
|
|
|
|
y = z = 0.0f;
|
|
// X moves
|
|
for( x = -2.0f; x <= 2.0f; x += 2.0f )
|
|
{
|
|
rgv3tStuckTable[idx].x = x;
|
|
rgv3tStuckTable[idx].y = y;
|
|
rgv3tStuckTable[idx].z = z;
|
|
idx++;
|
|
}
|
|
|
|
// Remaining multi axis nudges.
|
|
for( i = 0; i < 3; i++ )
|
|
{
|
|
z = zi[i];
|
|
|
|
for( x = -2.0f; x <= 2.0f; x += 2.0f )
|
|
{
|
|
for( y = -2.0f; y <= 2.0f; y += 2.0f )
|
|
{
|
|
rgv3tStuckTable[idx][0] = x;
|
|
rgv3tStuckTable[idx][1] = y;
|
|
rgv3tStuckTable[idx][2] = z;
|
|
idx++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
=============================================================
|
|
This module implements the shared player physics code between any particular game and
|
|
the engine. The same PM_Move routine is built into the game .dll and the client .dll and is
|
|
invoked by each side as appropriate. There should be no distinction, internally, between server
|
|
and client. This will ensure that prediction behaves appropriately.
|
|
=============================================================
|
|
*/
|
|
void PM_Move( playermove_t *ppmove, int server )
|
|
{
|
|
assert( pm_shared_initialized );
|
|
|
|
pmove = ppmove;
|
|
|
|
PM_PlayerMove(( server != 0 ) ? true : false );
|
|
|
|
if( pmove->onground != NULL )
|
|
{
|
|
pev->flags |= FL_ONGROUND;
|
|
}
|
|
else
|
|
{
|
|
pev->flags &= ~FL_ONGROUND;
|
|
}
|
|
|
|
// In single player, reset friction after each movement to FrictionModifier Triggers work still.
|
|
if( !pmove->multiplayer && ( pev->movetype == MOVETYPE_WALK ))
|
|
{
|
|
pev->friction = 1.0f;
|
|
}
|
|
}
|
|
|
|
void PM_Init( playermove_t *ppmove )
|
|
{
|
|
assert( !pm_shared_initialized );
|
|
|
|
pmove = ppmove;
|
|
|
|
PM_CreateStuckTable();
|
|
PM_InitTextureTypes();
|
|
|
|
pm_shared_initialized = 1;
|
|
} |