jkxr/Projects/Android/jni/OpenJK/codeJK2/game/g_active.cpp
Simon 12d117f679 Prevent cameras and turrets exiting if player moves their head too much
also.. for the secrity cameras, it will show the actual angles of the camera, but in mono.
2022-10-25 23:12:20 +01:00

3068 lines
84 KiB
C++

/*
===========================================================================
Copyright (C) 1999 - 2005, Id Software, Inc.
Copyright (C) 2000 - 2013, Raven Software, Inc.
Copyright (C) 2001 - 2013, Activision, Inc.
Copyright (C) 2013 - 2015, OpenJK contributors
This file is part of the OpenJK source code.
OpenJK is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
===========================================================================
*/
#include "g_headers.h"
#include "g_local.h"
#include "g_functions.h"
#include "../cgame/cg_local.h"
#include "Q3_Interface.h"
#include "wp_saber.h"
#include "g_icarus.h"
#ifdef _DEBUG
#include <float.h>
#endif //_DEBUG
#define SLOWDOWN_DIST 128.0f
#define MIN_NPC_SPEED 16.0f
extern qboolean Q3_TaskIDPending( gentity_t *ent, taskID_t taskType );
extern void G_MaintainFormations(gentity_t *self);
extern void BG_CalculateOffsetAngles( gentity_t *ent, usercmd_t *ucmd );//in bg_pangles.cpp
extern void TryUse( gentity_t *ent );
extern void ChangeWeapon( gentity_t *ent, int newWeapon );
extern void ScoreBoardReset(void);
extern void WP_SaberReflectCheck( gentity_t *self, usercmd_t *ucmd );
extern void WP_SaberUpdate( gentity_t *self, usercmd_t *ucmd );
extern void WP_SaberStartMissileBlockCheck( gentity_t *self, usercmd_t *ucmd );
extern void WP_ForcePowersUpdate( gentity_t *self, usercmd_t *ucmd );
extern void WP_SaberInitBladeData( gentity_t *ent );
extern gentity_t *SeekerAcquiresTarget ( gentity_t *ent, vec3_t pos );
extern void FireSeeker( gentity_t *owner, gentity_t *target, vec3_t origin, vec3_t dir );
extern qboolean InFront( vec3_t spot, vec3_t from, vec3_t fromAngles, float threshHold = 0.0f );
extern void NPC_SetLookTarget( gentity_t *self, int entNum, int clearTime );
extern qboolean PM_AdjustAnglesToGripper( gentity_t *gent, usercmd_t *cmd );
extern qboolean PM_AdjustAngleForWallRun( gentity_t *ent, usercmd_t *ucmd, qboolean doMove );
extern qboolean PM_AdjustAnglesForSpinningFlip( gentity_t *ent, usercmd_t *ucmd, qboolean anglesOnly );
extern qboolean PM_AdjustAnglesForBackAttack( gentity_t *ent, usercmd_t *ucmd );
extern qboolean PM_AdjustAnglesForSaberLock( gentity_t *ent, usercmd_t *ucmd );
extern qboolean PM_AdjustAnglesForKnockdown( gentity_t *ent, usercmd_t *ucmd, qboolean angleClampOnly );
extern qboolean PM_HasAnimation( gentity_t *ent, int animation );
extern qboolean PM_SpinningSaberAnim( int anim );
extern qboolean PM_SaberInAttack( int move );
extern int PM_AnimLength( int index, animNumber_t anim );
extern qboolean PM_InKnockDown( playerState_t *ps );
extern qboolean PM_InRoll( playerState_t *ps );
extern void PM_CmdForRoll( int anim, usercmd_t *pCmd );
extern qboolean PM_CrouchAnim( int anim );
extern qboolean PM_FlippingAnim( int anim );
extern qboolean PM_InCartwheel( int anim );
extern qboolean PM_StandingAnim( int anim );
extern qboolean PM_InForceGetUp( playerState_t *ps );
extern void G_CreateG2AttachedWeaponModel( gentity_t *ent, const char *weaponModel );
extern qboolean FlyingCreature( gentity_t *ent );
extern bool in_camera;
extern qboolean player_locked;
extern qboolean stop_icarus;
extern cvar_t *g_spskill;
extern cvar_t *g_timescale;
extern cvar_t *g_saberMoveSpeed;
extern cvar_t *g_saberAutoBlocking;
extern vmCvar_t cg_thirdPersonAlpha;
extern vmCvar_t cg_thirdPersonAutoAlpha;
void ClientEndPowerUps( gentity_t *ent );
int G_FindLookItem( gentity_t *self )
{
//FIXME: should be a more intelligent way of doing this, like auto aim?
//closest, most in front... did damage to... took damage from? How do we know who the player is focusing on?
gentity_t *ent;
int bestEntNum = ENTITYNUM_NONE;
gentity_t *entityList[MAX_GENTITIES];
int numListedEntities;
vec3_t center, mins, maxs, fwdangles, forward, dir;
int i, e;
float radius = 256;
float rating, bestRating = 0.0f;
//FIXME: no need to do this in 1st person?
fwdangles[1] = self->client->ps.viewangles[1];
AngleVectors( fwdangles, forward, NULL, NULL );
VectorCopy( self->currentOrigin, center );
for ( i = 0 ; i < 3 ; i++ )
{
mins[i] = center[i] - radius;
maxs[i] = center[i] + radius;
}
numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
if ( !numListedEntities )
{
return ENTITYNUM_NONE;
}
for ( e = 0 ; e < numListedEntities ; e++ )
{
ent = entityList[ e ];
if ( !ent->item )
{
continue;
}
if ( ent->s.eFlags&EF_NODRAW )
{
continue;
}
if ( (ent->spawnflags&4/*ITMSF_MONSTER*/) )
{//NPCs only
continue;
}
if ( !BG_CanItemBeGrabbed( &ent->s, &self->client->ps ) )
{//don't need it
continue;
}
if ( !gi.inPVS( self->currentOrigin, ent->currentOrigin ) )
{//not even potentially visible
continue;
}
if ( !G_ClearLOS( self, self->client->renderInfo.eyePoint, ent ) )
{//can't see him
continue;
}
//rate him based on how close & how in front he is
VectorSubtract( ent->currentOrigin, center, dir );
rating = (1.0f-(VectorNormalize( dir )/radius));
rating *= DotProduct( forward, dir );
if ( ent->item->giType == IT_HOLDABLE && ent->item->giTag == INV_SECURITY_KEY )
{//security keys are of the highest importance
rating *= 2.0f;
}
if ( rating > bestRating )
{
bestEntNum = ent->s.number;
bestRating = rating;
}
}
return bestEntNum;
}
extern void CG_SetClientViewAngles( vec3_t angles, qboolean overrideViewEnt );
qboolean G_ClearViewEntity( gentity_t *ent )
{
if ( !ent->client->ps.viewEntity )
return qfalse;
if ( ent->client->ps.viewEntity > 0 && ent->client->ps.viewEntity < ENTITYNUM_NONE )
{
if ( &g_entities[ent->client->ps.viewEntity] )
{
g_entities[ent->client->ps.viewEntity].svFlags &= ~SVF_BROADCAST;
if ( g_entities[ent->client->ps.viewEntity].NPC )
{
g_entities[ent->client->ps.viewEntity].NPC->controlledTime = 0;
SetClientViewAngle( &g_entities[ent->client->ps.viewEntity], g_entities[ent->client->ps.viewEntity].currentAngles );
G_SetAngles( &g_entities[ent->client->ps.viewEntity], g_entities[ent->client->ps.viewEntity].currentAngles );
VectorCopy( g_entities[ent->client->ps.viewEntity].currentAngles, g_entities[ent->client->ps.viewEntity].NPC->lastPathAngles );
g_entities[ent->client->ps.viewEntity].NPC->desiredYaw = g_entities[ent->client->ps.viewEntity].currentAngles[YAW];
}
}
CG_SetClientViewAngles( ent->pos4, qtrue );
SetClientViewAngle( ent, ent->pos4 );
}
ent->client->ps.viewEntity = 0;
return qtrue;
}
void G_SetViewEntity( gentity_t *self, gentity_t *viewEntity )
{
if ( !self || !self->client || !viewEntity )
{
return;
}
if ( self->s.number == 0 && cg.zoomMode )
{
// yeah, it should really toggle them so it plays the end sound....
cg.zoomMode = 0;
}
if ( viewEntity->s.number == self->client->ps.viewEntity )
{
return;
}
//clear old one first
G_ClearViewEntity( self );
//set new one
self->client->ps.viewEntity = viewEntity->s.number;
viewEntity->svFlags |= SVF_BROADCAST;
//remember current angles
VectorCopy( self->client->ps.viewangles, self->pos4 );
if ( viewEntity->client )
{
//vec3_t clear = {0,0,0};
CG_SetClientViewAngles( viewEntity->client->ps.viewangles, qtrue );
//SetClientViewAngle( self, viewEntity->client->ps.viewangles );
//SetClientViewAngle( viewEntity, clear );
/*
VectorCopy( viewEntity->client->ps.viewangles, self->client->ps.viewangles );
for ( int i = 0; i < 3; i++ )
{
self->client->ps.delta_angles[i] = viewEntity->client->ps.delta_angles[i];
}
*/
}
if ( !self->s.number )
{
CG_CenterPrint( "@INGAME_EXIT_VIEW", SCREEN_HEIGHT * 0.95 );
}
}
qboolean G_ControlledByPlayer( gentity_t *self )
{
if ( self && self->NPC && self->NPC->controlledTime > level.time )
{//being controlled
gentity_t *controller = &g_entities[0];
if ( controller->client && controller->client->ps.viewEntity == self->s.number )
{//we're the player's viewEntity
return qtrue;
}
}
return qfalse;
}
qboolean G_ValidateLookEnemy( gentity_t *self, gentity_t *enemy )
{
if ( !enemy )
{
return qfalse;
}
if ( enemy->flags&FL_NOTARGET )
{
return qfalse;
}
if ( !enemy || enemy == self || !enemy->inuse )
{
return qfalse;
}
if ( !enemy->client || !enemy->NPC )
{//not valid
if ( (enemy->svFlags&SVF_NONNPC_ENEMY)
&& enemy->s.weapon == WP_TURRET
&& enemy->noDamageTeam != self->client->playerTeam
&& enemy->health > 0 )
{//a turret
//return qtrue;
}
else
{
return qfalse;
}
}
else
{
if ( enemy->client->playerTeam == self->client->playerTeam )
{//on same team
return qfalse;
}
if ( enemy->health <= 0 && ((level.time-enemy->s.time) > 3000||!InFront(enemy->currentOrigin,self->currentOrigin,self->client->ps.viewangles,0.2f)||DistanceHorizontal(enemy->currentOrigin,self->currentOrigin)>16384))//>128
{//corpse, been dead too long or too out of sight to be interesting
if ( !enemy->message )
{
return qfalse;
}
}
}
if ( (!InFront( enemy->currentOrigin, self->currentOrigin, self->client->ps.viewangles, 0.0f) || !G_ClearLOS( self, self->client->renderInfo.eyePoint, enemy ) )
&& ( DistanceHorizontalSquared( enemy->currentOrigin, self->currentOrigin ) > 65536 || fabs(enemy->currentOrigin[2]-self->currentOrigin[2]) > 384 ) )
{//(not in front or not clear LOS) & greater than 256 away
return qfalse;
}
//LOS?
return qtrue;
}
void G_ChooseLookEnemy( gentity_t *self, usercmd_t *ucmd )
{
//FIXME: should be a more intelligent way of doing this, like auto aim?
//closest, most in front... did damage to... took damage from? How do we know who the player is focusing on?
gentity_t *ent, *bestEnt = NULL;
gentity_t *entityList[MAX_GENTITIES];
int numListedEntities;
vec3_t center, mins, maxs, fwdangles, forward, dir;
int i, e;
float radius = 256;
float rating, bestRating = 0.0f;
//FIXME: no need to do this in 1st person?
fwdangles[0] = 0; //Must initialize data!
fwdangles[1] = self->client->ps.viewangles[1];
fwdangles[2] = 0;
AngleVectors( fwdangles, forward, NULL, NULL );
VectorCopy( self->currentOrigin, center );
for ( i = 0 ; i < 3 ; i++ )
{
mins[i] = center[i] - radius;
maxs[i] = center[i] + radius;
}
numListedEntities = gi.EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES );
if ( !numListedEntities )
{//should we clear the enemy?
return;
}
for ( e = 0 ; e < numListedEntities ; e++ )
{
ent = entityList[ e ];
if ( !gi.inPVS( self->currentOrigin, ent->currentOrigin ) )
{//not even potentially visible
continue;
}
if ( !G_ValidateLookEnemy( self, ent ) )
{//doesn't meet criteria of valid look enemy (don't check current since we would have done that before this func's call
continue;
}
if ( !G_ClearLOS( self, self->client->renderInfo.eyePoint, ent ) )
{//can't see him
continue;
}
//rate him based on how close & how in front he is
VectorSubtract( ent->currentOrigin, center, dir );
rating = (1.0f-(VectorNormalize( dir )/radius));
rating *= DotProduct( forward, dir )+1.0f;
if ( ent->health <= 0 )
{
if ( (ucmd->buttons&BUTTON_ATTACK) || (ucmd->buttons&BUTTON_ALT_ATTACK) )
{//if attacking, don't consider dead enemies
continue;
}
if ( ent->message )
{//keyholder
rating *= 0.5f;
}
else
{
rating *= 0.1f;
}
}
if ( ent->s.weapon == WP_SABER )
{
rating *= 2.0f;
}
if ( ent->enemy == self )
{//he's mad at me, he's more important
rating *= 2.0f;
}
else if ( ent->NPC && ent->NPC->blockedSpeechDebounceTime > level.time - 6000 )
{//he's detected me, he's more important
if ( ent->NPC->blockedSpeechDebounceTime > level.time + 4000 )
{
rating *= 1.5f;
}
else
{//from 1.0f to 1.5f
rating += rating * ((float)(ent->NPC->blockedSpeechDebounceTime-level.time) + 6000.0f)/20000.0f;
}
}
/*
if ( g_crosshairEntNum == ent && !self->enemy )
{//we don't have an enemy and we are aiming at this guy
rading *= 2.0f;
}
*/
if ( rating > bestRating )
{
bestEnt = ent;
bestRating = rating;
}
}
if ( bestEnt )
{
self->enemy = bestEnt;
}
}
/*
===============
G_DamageFeedback
Called just before a snapshot is sent to the given player.
Totals up all damage and generates both the player_state_t
damage values to that client for pain blends and kicks, and
global pain sound events for all clients.
===============
*/
void P_DamageFeedback( gentity_t *player ) {
gclient_t *client;
float count;
vec3_t angles;
client = player->client;
if ( client->ps.pm_type == PM_DEAD ) {
return;
}
// total points of damage shot at the player this frame
count = client->damage_blood + client->damage_armor;
if ( count == 0 ) {
return; // didn't take any damage
}
if ( count > 255 ) {
count = 255;
}
// send the information to the client
// world damage (falling, slime, etc) uses a special code
// to make the blend blob centered instead of positional
if ( client->damage_fromWorld )
{
client->ps.damagePitch = 255;
client->ps.damageYaw = 255;
client->damage_fromWorld = qfalse;
}
else
{
vectoangles( client->damage_from, angles );
client->ps.damagePitch = angles[PITCH]/360.0 * 256;
client->ps.damageYaw = angles[YAW]/360.0 * 256;
}
client->ps.damageCount = count;
//
// clear totals
//
client->damage_blood = 0;
client->damage_armor = 0;
client->damage_knockback = 0;
}
/*
=============
P_WorldEffects
Check for lava / slime contents and drowning
=============
*/
void P_WorldEffects( gentity_t *ent ) {
int mouthContents = 0;
if ( ent->client->noclip )
{
ent->client->airOutTime = level.time + 12000; // don't need air
return;
}
if ( !in_camera )
{
mouthContents = gi.pointcontents( ent->client->renderInfo.eyePoint, ent->s.number );
}
//
// check for drowning
//
if ( (mouthContents&(CONTENTS_WATER|CONTENTS_SLIME)) )
{
if ( ent->client->NPC_class == CLASS_SWAMPTROOPER )
{//they have air tanks
ent->client->airOutTime = level.time + 12000; // don't need air
ent->damage = 2;
}
else if ( ent->client->airOutTime < level.time)
{// if out of air, start drowning
// drown!
ent->client->airOutTime += 1000;
if ( ent->health > 0 ) {
// take more damage the longer underwater
ent->damage += 2;
if (ent->damage > 15)
ent->damage = 15;
// play a gurp sound instead of a normal pain sound
if (ent->health <= ent->damage)
{
G_AddEvent( ent, EV_WATER_DROWN, 0 );
}
else
{
G_AddEvent( ent, Q_irand(EV_WATER_GURP1, EV_WATER_GURP2), 0 );
}
// don't play a normal pain sound
ent->painDebounceTime = level.time + 200;
G_Damage (ent, NULL, NULL, NULL, NULL,
ent->damage, DAMAGE_NO_ARMOR, MOD_WATER);
}
}
}
else
{
ent->client->airOutTime = level.time + 12000;
ent->damage = 2;
}
//
// check for sizzle damage (move to pmove?)
//
if (ent->waterlevel &&
(ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) {
if (ent->health > 0
&& ent->painDebounceTime < level.time ) {
if (ent->watertype & CONTENTS_LAVA) {
G_Damage (ent, NULL, NULL, NULL, NULL,
15*ent->waterlevel, 0, MOD_LAVA);
}
if (ent->watertype & CONTENTS_SLIME) {
G_Damage (ent, NULL, NULL, NULL, NULL,
1, 0, MOD_SLIME);
}
}
}
// Poisoned?
if ((ent->client->poisonDamage) && (ent->client->poisonTime < level.time))
{
ent->client->poisonDamage -= 2;
ent->client->poisonTime = level.time + 1000;
G_Damage( ent, NULL, NULL, 0, 0, 2, DAMAGE_NO_KNOCKBACK|DAMAGE_NO_ARMOR, MOD_UNKNOWN );//FIXME: MOD_POISON?
if (ent->client->poisonDamage<0)
{
ent->client->poisonDamage = 0;
}
}
}
/*
===============
G_SetClientSound
===============
*/
void G_SetClientSound( gentity_t *ent ) {
// if (ent->waterlevel && (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) )
// ent->s.loopSound = G_SoundIndex("sound/weapons/stasis/electricloop.wav");
// else
// ent->s.loopSound = 0;
}
//==============================================================
void DoImpact( gentity_t *self, gentity_t *other, qboolean damageSelf )
{
float magnitude, my_mass;
vec3_t velocity;
if( self->client )
{
VectorCopy( self->client->ps.velocity, velocity );
my_mass = self->mass;
}
else
{
VectorCopy( self->s.pos.trDelta, velocity );
if ( self->s.pos.trType == TR_GRAVITY )
{
velocity[2] -= 0.25f * g_gravity->value;
}
if( !self->mass )
{
my_mass = 1;
}
else if ( self->mass <= 10 )
{
my_mass = 10;
}
else
{
my_mass = self->mass;///10;
}
}
magnitude = VectorLength( velocity ) * my_mass / 50;
if ( !self->client || self->client->ps.lastOnGround+300<level.time || ( self->client->ps.lastOnGround+100 < level.time ) )
{
vec3_t dir1, dir2;
float force = 0, dot;
if ( other->material == MAT_GLASS || other->material == MAT_GLASS_METAL || other->material == MAT_GRATE1 || ((other->svFlags&SVF_BBRUSH)&&(other->spawnflags&4/*THIN*/)) )//(other->absmax[0]-other->absmin[0]<=32||other->absmax[1]-other->absmin[1]<=32||other->absmax[2]-other->absmin[2]<=32)) )
{//glass and thin breakable brushes (axially aligned only, unfortunately) take more impact damage
magnitude *= 2;
}
//damage them
if ( magnitude >= 100 && other->s.number < ENTITYNUM_WORLD )
{
VectorCopy( velocity, dir1 );
VectorNormalize( dir1 );
if( VectorCompare( other->currentOrigin, vec3_origin ) )
{//a brush with no origin
VectorCopy ( dir1, dir2 );
}
else
{
VectorSubtract( other->currentOrigin, self->currentOrigin, dir2 );
VectorNormalize( dir2 );
}
dot = DotProduct( dir1, dir2 );
if ( dot >= 0.2 )
{
force = dot;
}
else
{
force = 0;
}
force *= (magnitude/50);
int cont = gi.pointcontents( other->absmax, other->s.number );
if( (cont&CONTENTS_WATER) )//|| (self.classname=="barrel"&&self.aflag))//FIXME: or other watertypes
{
force /= 3; //water absorbs 2/3 velocity
}
if ( self->NPC && other->s.number == ENTITYNUM_WORLD )
{//NPCs take less damage
force /= 2;
}
/*
if(self.frozen>0&&force>10)
force=10;
*/
if( ( force >= 1 && other->s.number != 0 ) || force >= 10)
{
/*
dprint("Damage other (");
dprint(loser.classname);
dprint("): ");
dprint(ftos(force));
dprint("\n");
*/
if ( other->svFlags & SVF_GLASS_BRUSH )
{
other->splashRadius = (float)(self->maxs[0] - self->mins[0])/4.0f;
}
if ( self->forcePushTime > level.time - 1000//was force pushed/pulled in the last 1600 milliseconds
&& self->forcePuller == other->s.number )//hit the person who pushed/pulled me
{//ignore the impact
}
else if ( other->takedamage )
{
if ( !self->client || !other->s.number || !other->client )
{//aw, fuck it, clients no longer take impact damage from other clients, unless you're the player
G_Damage( other, self, self, velocity, self->currentOrigin, force, DAMAGE_NO_ARMOR, MOD_IMPACT );
}
else
{
GEntity_PainFunc( other, self, self, self->currentOrigin, force, MOD_IMPACT );
//Hmm, maybe knockdown?
G_Throw( other, dir2, force );
}
}
else
{
//Hmm, maybe knockdown?
G_Throw( other, dir2, force );
}
}
}
if ( damageSelf && self->takedamage && !(self->flags&FL_NO_IMPACT_DMG))
{
//Now damage me
//FIXME: more lenient falling damage, especially for when driving a vehicle
if ( self->client && self->client->ps.forceJumpZStart )
{//we were force-jumping
if ( self->currentOrigin[2] >= self->client->ps.forceJumpZStart )
{//we landed at same height or higher than we landed
magnitude = 0;
}
else
{//FIXME: take off some of it, at least?
magnitude = (self->client->ps.forceJumpZStart-self->currentOrigin[2])/3;
}
}
//if(self.classname!="monster_mezzoman"&&self.netname!="spider")//Cats always land on their feet
if( ( magnitude >= 100 + self->health && self->s.number != 0 && self->s.weapon != WP_SABER ) || ( magnitude >= 700 ) )//&& self.safe_time < level.time ))//health here is used to simulate structural integrity
{
if ( (self->s.weapon == WP_SABER || self->s.number == 0) && self->client && self->client->ps.groundEntityNum < ENTITYNUM_NONE && magnitude < 1000 )
{//players and jedi take less impact damage
//allow for some lenience on high falls
magnitude /= 2;
/*
if ( self.absorb_time >= time )//crouching on impact absorbs 1/2 the damage
{
magnitude/=2;
}
*/
}
magnitude /= 40;
magnitude = magnitude - force/2;//If damage other, subtract half of that damage off of own injury
if ( magnitude >= 1 )
{
//FIXME: Put in a thingtype impact sound function
/*
dprint("Damage self (");
dprint(self.classname);
dprint("): ");
dprint(ftos(magnitude));
dprint("\n");
*/
/*
if ( self.classname=="player_sheep "&& self.flags&FL_ONGROUND && self.velocity_z > -50 )
return;
*/
if ( self->NPC && self->s.weapon == WP_SABER )
{//FIXME: for now Jedi take no falling damage, but really they should if pushed off?
magnitude = 0;
}
G_Damage( self, NULL, NULL, NULL, self->currentOrigin, magnitude/2, DAMAGE_NO_ARMOR, MOD_FALLING );//FIXME: MOD_IMPACT
}
}
}
//FIXME: slow my velocity some?
self->lastImpact = level.time;
/*
if(self.flags&FL_ONGROUND)
self.last_onground=time;
*/
}
}
/*
==============
ClientImpacts
==============
*/
void ClientImpacts( gentity_t *ent, pmove_t *pm ) {
int i, j;
trace_t trace;
gentity_t *other;
memset( &trace, 0, sizeof( trace ) );
for (i=0 ; i<pm->numtouch ; i++) {
for (j=0 ; j<i ; j++) {
if (pm->touchents[j] == pm->touchents[i] ) {
break;
}
}
if (j != i) {
continue; // duplicated
}
other = &g_entities[ pm->touchents[i] ];
if ( ( ent->NPC != NULL ) && ( ent->e_TouchFunc != touchF_NULL ) ) { // last check unneccessary
GEntity_TouchFunc( ent, other, &trace );
}
if ( other->e_TouchFunc == touchF_NULL ) { // not needed, but I'll leave it I guess (cache-hit issues)
continue;
}
GEntity_TouchFunc( other, ent, &trace );
}
}
/*
============
G_TouchTriggersLerped
Find all trigger entities that ent's current position touches.
Spectators will only interact with teleporters.
This version checks at 6 unit steps between last and current origins
============
*/
void G_TouchTriggersLerped( gentity_t *ent ) {
int i, num;
float dist, curDist = 0;
gentity_t *touch[MAX_GENTITIES], *hit;
trace_t trace;
vec3_t end, mins, maxs, diff;
const vec3_t range = { 40, 40, 52 };
qboolean touched[MAX_GENTITIES];
qboolean done = qfalse;
if ( !ent->client ) {
return;
}
// dead NPCs don't activate triggers!
if ( ent->client->ps.stats[STAT_HEALTH] <= 0 ) {
if ( ent->s.number )
{
return;
}
}
#ifdef _DEBUG
for ( int j = 0; j < 3; j++ )
{
assert( !Q_isnan(ent->currentOrigin[j]));
assert( !Q_isnan(ent->lastOrigin[j]));
}
#endif// _DEBUG
VectorSubtract( ent->currentOrigin, ent->lastOrigin, diff );
dist = VectorNormalize( diff );
memset (touched, qfalse, sizeof(touched) );
for ( curDist = 0; !done && ent->maxs[1]>0; curDist += (float)ent->maxs[1]/2.0f )
{
if ( curDist >= dist )
{
VectorCopy( ent->currentOrigin, end );
done = qtrue;
}
else
{
VectorMA( ent->lastOrigin, curDist, diff, end );
}
VectorSubtract( end, range, mins );
VectorAdd( end, range, maxs );
num = gi.EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
// can't use ent->absmin, because that has a one unit pad
VectorAdd( end, ent->mins, mins );
VectorAdd( end, ent->maxs, maxs );
for ( i=0 ; i<num ; i++ ) {
hit = touch[i];
if ( (hit->e_TouchFunc == touchF_NULL) && (ent->e_TouchFunc == touchF_NULL) ) {
continue;
}
if ( !( hit->contents & CONTENTS_TRIGGER ) ) {
continue;
}
if ( touched[i] == qtrue ) {
continue;//already touched this move
}
if ( ent->client->ps.stats[STAT_HEALTH] <= 0 )
{
if ( Q_stricmp( "trigger_teleport", hit->classname ) || !(hit->spawnflags&16/*TTSF_DEAD_OK*/) )
{//dead clients can only touch tiogger_teleports that are marked as touchable
continue;
}
}
// use seperate code for determining if an item is picked up
// so you don't have to actually contact its bounding box
/*
if ( hit->s.eType == ET_ITEM ) {
if ( !BG_PlayerTouchesItem( &ent->client->ps, &hit->s, level.time ) ) {
continue;
}
} else */
{
if ( !gi.EntityContact( mins, maxs, hit ) ) {
continue;
}
}
touched[i] = qtrue;
memset( &trace, 0, sizeof(trace) );
if ( hit->e_TouchFunc != touchF_NULL ) {
GEntity_TouchFunc(hit, ent, &trace);
}
//WTF? Why would a trigger ever fire off the NPC's touch func??!!!
/*
if ( ( ent->NPC != NULL ) && ( ent->e_TouchFunc != touchF_NULL ) ) {
GEntity_TouchFunc( ent, hit, &trace );
}
*/
}
}
}
/*
============
G_TouchTriggers
Find all trigger entities that ent's current position touches.
Spectators will only interact with teleporters.
============
*/
void G_TouchTriggers( gentity_t *ent ) {
int i, num;
gentity_t *touch[MAX_GENTITIES], *hit;
trace_t trace;
vec3_t mins, maxs;
const vec3_t range = { 40, 40, 52 };
if ( !ent->client ) {
return;
}
// dead clients don't activate triggers!
if ( ent->client->ps.stats[STAT_HEALTH] <= 0 ) {
return;
}
VectorSubtract( ent->client->ps.origin, range, mins );
VectorAdd( ent->client->ps.origin, range, maxs );
num = gi.EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
// can't use ent->absmin, because that has a one unit pad
VectorAdd( ent->client->ps.origin, ent->mins, mins );
VectorAdd( ent->client->ps.origin, ent->maxs, maxs );
for ( i=0 ; i<num ; i++ ) {
hit = touch[i];
if ( (hit->e_TouchFunc == touchF_NULL) && (ent->e_TouchFunc == touchF_NULL) ) {
continue;
}
if ( !( hit->contents & CONTENTS_TRIGGER ) ) {
continue;
}
// use seperate code for determining if an item is picked up
// so you don't have to actually contact its bounding box
/*
if ( hit->s.eType == ET_ITEM ) {
if ( !BG_PlayerTouchesItem( &ent->client->ps, &hit->s, level.time ) ) {
continue;
}
} else */
{
if ( !gi.EntityContact( mins, maxs, hit ) ) {
continue;
}
}
memset( &trace, 0, sizeof(trace) );
if ( hit->e_TouchFunc != touchF_NULL ) {
GEntity_TouchFunc(hit, ent, &trace);
}
if ( ( ent->NPC != NULL ) && ( ent->e_TouchFunc != touchF_NULL ) ) {
GEntity_TouchFunc( ent, hit, &trace );
}
}
}
/*
============
G_MoverTouchTriggers
Find all trigger entities that ent's current position touches.
Spectators will only interact with teleporters.
============
*/
void G_MoverTouchPushTriggers( gentity_t *ent, vec3_t oldOrg )
{
int i, num;
float step, stepSize, dist;
gentity_t *touch[MAX_GENTITIES], *hit;
trace_t trace;
vec3_t mins, maxs, dir, size, checkSpot;
const vec3_t range = { 40, 40, 52 };
// non-moving movers don't hit triggers!
if ( !VectorLengthSquared( ent->s.pos.trDelta ) )
{
return;
}
VectorSubtract( ent->mins, ent->maxs, size );
stepSize = VectorLength( size );
if ( stepSize < 1 )
{
stepSize = 1;
}
VectorSubtract( ent->currentOrigin, oldOrg, dir );
dist = VectorNormalize( dir );
for ( step = 0; step <= dist; step += stepSize )
{
VectorMA( ent->currentOrigin, step, dir, checkSpot );
VectorSubtract( checkSpot, range, mins );
VectorAdd( checkSpot, range, maxs );
num = gi.EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
// can't use ent->absmin, because that has a one unit pad
VectorAdd( checkSpot, ent->mins, mins );
VectorAdd( checkSpot, ent->maxs, maxs );
for ( i=0 ; i<num ; i++ )
{
hit = touch[i];
if ( hit->s.eType != ET_PUSH_TRIGGER )
{
continue;
}
if ( hit->e_TouchFunc == touchF_NULL )
{
continue;
}
if ( !( hit->contents & CONTENTS_TRIGGER ) )
{
continue;
}
if ( !gi.EntityContact( mins, maxs, hit ) )
{
continue;
}
memset( &trace, 0, sizeof(trace) );
if ( hit->e_TouchFunc != touchF_NULL )
{
GEntity_TouchFunc(hit, ent, &trace);
}
}
}
}
void G_MatchPlayerWeapon( gentity_t *ent )
{
if ( g_entities[0].inuse && g_entities[0].client )
{//player is around
int newWeap;
if ( g_entities[0].client->ps.weapon > WP_DET_PACK )
{
newWeap = WP_BRYAR_PISTOL;
}
else
{
newWeap = g_entities[0].client->ps.weapon;
}
if ( newWeap != WP_NONE && ent->client->ps.weapon != newWeap )
{
if ( ent->weaponModel >= 0 )
{
gi.G2API_RemoveGhoul2Model(ent->ghoul2, ent->weaponModel);
}
ent->client->ps.stats[STAT_WEAPONS] = ( 1 << newWeap );
ent->client->ps.ammo[weaponData[newWeap].ammoIndex] = 999;
ChangeWeapon( ent, newWeap );
ent->client->ps.weapon = newWeap;
ent->client->ps.weaponstate = WEAPON_READY;
if ( newWeap == WP_SABER )
{
//FIXME: AddSound/Sight Event
WP_SaberInitBladeData( ent );
G_CreateG2AttachedWeaponModel( ent, ent->client->ps.saberModel );
ent->client->ps.saberActive = g_entities[0].client->ps.saberActive;
ent->client->ps.saberLength = g_entities[0].client->ps.saberLength;
ent->client->ps.saberAnimLevel = g_entities[0].client->ps.saberAnimLevel;
}
else
{
G_CreateG2AttachedWeaponModel( ent, weaponData[newWeap].weaponMdl );
}
}
}
}
void G_NPCMunroMatchPlayerWeapon( gentity_t *ent )
{
//special uber hack for cinematic Munro's to match player's weapon
if ( !in_camera )
{
if ( ent && ent->client && ent->NPC && (ent->NPC->aiFlags&NPCAI_MATCHPLAYERWEAPON) )
{//we're a Kyle NPC
G_MatchPlayerWeapon( ent );
}
}
}
/*
=================
ClientInactivityTimer
Returns qfalse if the client is dropped
=================
*/
qboolean ClientInactivityTimer( gclient_t *client ) {
if ( ! g_inactivity->integer )
{
// give everyone some time, so if the operator sets g_inactivity during
// gameplay, everyone isn't kicked
client->inactivityTime = level.time + 60 * 1000;
client->inactivityWarning = qfalse;
}
else if ( client->usercmd.forwardmove ||
client->usercmd.rightmove ||
client->usercmd.upmove ||
(client->usercmd.buttons & BUTTON_ATTACK) ||
(client->usercmd.buttons & BUTTON_ALT_ATTACK) )
{
client->inactivityTime = level.time + g_inactivity->integer * 1000;
client->inactivityWarning = qfalse;
}
else if ( !client->pers.localClient )
{
if ( level.time > client->inactivityTime )
{
gi.DropClient( client - level.clients, "Dropped due to inactivity" );
return qfalse;
}
if ( level.time > client->inactivityTime - 10000 && !client->inactivityWarning )
{
client->inactivityWarning = qtrue;
gi.SendServerCommand( client - level.clients, "cp \"Ten seconds until inactivity drop!\n\"" );
}
}
else
{//FIXME: here is where we can decide to play an idle animation
}
return qtrue;
}
/*
==================
ClientTimerActions
Actions that happen once a second
==================
*/
void ClientTimerActions( gentity_t *ent, int msec ) {
gclient_t *client;
client = ent->client;
client->timeResidual += msec;
while ( client->timeResidual >= 1000 )
{
client->timeResidual -= 1000;
if ( ent->s.weapon != WP_NONE )
{
ent->client->sess.missionStats.weaponUsed[ent->s.weapon]++;
}
// if we've got the seeker powerup, see if we can shoot it at someone
/* if ( ent->client->ps.powerups[PW_SEEKER] > level.time )
{
vec3_t seekerPos, dir;
gentity_t *enemy = SeekerAcquiresTarget( ent, seekerPos );
if ( enemy != NULL ) // set the client's enemy to a valid target
{
FireSeeker( ent, enemy, seekerPos, dir );
gentity_t *tent;
tent = G_TempEntity( seekerPos, EV_POWERUP_SEEKER_FIRE );
VectorCopy( dir, tent->pos1 );
tent->s.eventParm = ent->s.number;
}
}*/
}
}
/*
====================
ClientIntermissionThink
====================
*/
static qboolean ClientCinematicThink( gclient_t *client ) {
client->ps.eFlags &= ~EF_FIRING;
// swap button actions
client->oldbuttons = client->buttons;
client->buttons = client->usercmd.buttons;
if ( client->buttons & ( BUTTON_USE ) & ( client->oldbuttons ^ client->buttons ) ) {
return( qtrue );
}
return( qfalse );
}
/*
================
ClientEvents
Events will be passed on to the clients for presentation,
but any server game effects are handled here
================
*/
extern void WP_SaberDamageTrace( gentity_t *ent );
extern void WP_SaberUpdateOldBladeData( gentity_t *ent );
void ClientEvents( gentity_t *ent, int oldEventSequence ) {
int i;
int event;
gclient_t *client;
//int damage;
#ifndef FINAL_BUILD
qboolean fired = qfalse;
#endif
client = ent->client;
for ( i = oldEventSequence ; i < client->ps.eventSequence ; i++ ) {
event = client->ps.events[ i & (MAX_PS_EVENTS-1) ];
switch ( event ) {
case EV_FALL_MEDIUM:
case EV_FALL_FAR://these come from bg_pmove, PM_CrashLand
if ( ent->s.eType != ET_PLAYER ) {
break; // not in the player model
}
/*
//FIXME: isn't there a more accurate way to calculate damage from falls?
if ( event == EV_FALL_FAR )
{
damage = 50;
}
else
{
damage = 25;
}
ent->painDebounceTime = level.time + 200; // no normal pain sound
G_Damage (ent, NULL, NULL, NULL, NULL, damage, 0, MOD_FALLING);
*/
break;
case EV_FIRE_WEAPON:
#ifndef FINAL_BUILD
if ( fired ) {
gi.Printf( "DOUBLE EV_FIRE_WEAPON AND-OR EV_ALT_FIRE!!\n" );
}
fired = qtrue;
#endif
FireWeapon( ent, qfalse );
break;
case EV_ALT_FIRE:
#ifndef FINAL_BUILD
if ( fired ) {
gi.Printf( "DOUBLE EV_FIRE_WEAPON AND-OR EV_ALT_FIRE!!\n" );
}
fired = qtrue;
#endif
FireWeapon( ent, qtrue );
break;
default:
break;
}
}
//by the way, if you have your saber in hand and it's on, do the damage trace
if ( client->ps.weapon == WP_SABER )
{
if ( g_timescale->value >= 1.0f || !(client->ps.forcePowersActive&(1<<FP_SPEED)) )
{
int wait = FRAMETIME/2;
//sanity check
if ( client->ps.saberDamageDebounceTime - level.time > wait )
{//when you unpause the game with force speed on, the time gets *really* wiggy...
client->ps.saberDamageDebounceTime = level.time + wait;
}
if ( client->ps.saberDamageDebounceTime <= level.time )
{
WP_SaberDamageTrace( ent );
WP_SaberUpdateOldBladeData( ent );
/*
if ( g_timescale->value&&client->ps.clientNum==0&&!player_locked&&!MatrixMode&&client->ps.forcePowersActive&(1<<FP_SPEED) )
{
wait = floor( (float)wait*g_timescale->value );
}
*/
client->ps.saberDamageDebounceTime = level.time + wait;
}
}
}
}
qboolean G_CheckClampUcmd( gentity_t *ent, usercmd_t *ucmd )
{
qboolean overridAngles = qfalse;
if ( (!ent->s.number&&ent->aimDebounceTime>level.time)
|| (ent->client->ps.pm_time && (ent->client->ps.pm_flags&PMF_TIME_KNOCKBACK))
|| ent->forcePushTime > level.time )
{//being knocked back, can't do anything!
ucmd->buttons = 0;
ucmd->forwardmove = 0;
ucmd->rightmove = 0;
ucmd->upmove = 0;
if ( ent->NPC )
{
VectorClear( ent->client->ps.moveDir );
}
}
overridAngles = (PM_AdjustAnglesForKnockdown( ent, ucmd, qfalse )?qtrue:overridAngles);
if ( ent->client->ps.saberLockTime > level.time )
{
ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0;
if ( ent->client->ps.saberLockTime - level.time > SABER_LOCK_DELAYED_TIME )
{//2 second delay before either can push
//FIXME: base on difficulty
ucmd->buttons = 0;
}
else
{
ucmd->buttons &= ~(ucmd->buttons&~BUTTON_ATTACK);
}
overridAngles = (PM_AdjustAnglesForSaberLock( ent, ucmd )?qtrue:overridAngles);
if ( ent->NPC )
{
VectorClear( ent->client->ps.moveDir );
}
}
if ( ent->client->ps.saberMove == LS_A_LUNGE )
{//can't move during lunge
ucmd->rightmove = ucmd->upmove = 0;
if ( ent->client->ps.legsAnimTimer > 500 && (ent->s.number || !player_locked) )
{
ucmd->forwardmove = 127;
}
else
{
ucmd->forwardmove = 0;
}
if ( ent->NPC )
{//invalid now
VectorClear( ent->client->ps.moveDir );
}
}
if ( ent->client->ps.saberMove == LS_A_JUMP_T__B_ )
{//can't move during leap
if ( ent->client->ps.groundEntityNum != ENTITYNUM_NONE || (!ent->s.number && player_locked) )
{//hit the ground
ucmd->forwardmove = 0;
}
ucmd->rightmove = ucmd->upmove = 0;
if ( ent->NPC )
{//invalid now
VectorClear( ent->client->ps.moveDir );
}
}
if ( ent->client->ps.saberMove == LS_A_BACK || ent->client->ps.saberMove == LS_A_BACK_CR
|| ent->client->ps.saberMove == LS_A_BACKSTAB )
{//can't move or turn during back attacks
ucmd->forwardmove = ucmd->rightmove = 0;
if ( ent->NPC )
{
VectorClear( ent->client->ps.moveDir );
}
if ( (overridAngles = (PM_AdjustAnglesForBackAttack( ent, ucmd )?qtrue:overridAngles)) == qtrue )
{
//pull back the view
if ( !ent->s.number )
{
float animLength = PM_AnimLength( ent->client->clientInfo.animFileIndex, (animNumber_t)ent->client->ps.torsoAnim );
float elapsedTime = (float)(animLength-ent->client->ps.legsAnimTimer);
float backDist = 0;
if ( elapsedTime < animLength/2.0f )
{//starting anim
backDist = (elapsedTime/animLength)*120.0f;
}
else
{//ending anim
backDist = ((animLength-elapsedTime)/animLength)*120.0f;
}
cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_RNG;
cg.overrides.thirdPersonRange = cg_thirdPersonRange.value+backDist;
}
}
}
else if ( ent->client->ps.torsoAnim == BOTH_WALL_FLIP_BACK1
|| ent->client->ps.torsoAnim == BOTH_WALL_FLIP_BACK2 )
{
//pull back the view
if ( !ent->s.number )
{
float animLength = PM_AnimLength( ent->client->clientInfo.animFileIndex, (animNumber_t)ent->client->ps.torsoAnim );
float elapsedTime = (float)(animLength-ent->client->ps.legsAnimTimer);
float backDist = 0;
if ( elapsedTime < animLength/2.0f )
{//starting anim
backDist = (elapsedTime/animLength)*120.0f;
}
else
{//ending anim
backDist = ((animLength-elapsedTime)/animLength)*120.0f;
}
cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_RNG;
cg.overrides.thirdPersonRange = cg_thirdPersonRange.value+backDist;
}
}
else if ( !ent->s.number )
{
if ( ent->client->NPC_class != CLASS_ATST )
{
cg.overrides.active &= ~CG_OVERRIDE_3RD_PERSON_RNG;
cg.overrides.thirdPersonRange = 0;
}
}
if ( PM_InRoll( &ent->client->ps ) )
{
if ( ent->s.number || !player_locked )
{
PM_CmdForRoll( ent->client->ps.legsAnim, ucmd );
}
if ( ent->NPC )
{//invalid now
VectorClear( ent->client->ps.moveDir );
}
ent->client->ps.speed = 400;
}
if ( PM_InCartwheel( ent->client->ps.legsAnim ) )
{//can't keep moving in cartwheel
ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0;
if ( ent->NPC )
{//invalid now
VectorClear( ent->client->ps.moveDir );
}
if ( ent->s.number || !player_locked )
{
switch ( ent->client->ps.legsAnim )
{
case BOTH_ARIAL_LEFT:
case BOTH_CARTWHEEL_LEFT:
ucmd->rightmove = -127;
break;
case BOTH_ARIAL_RIGHT:
case BOTH_CARTWHEEL_RIGHT:
ucmd->rightmove = 127;
break;
case BOTH_ARIAL_F1:
ucmd->forwardmove = 127;
break;
default:
break;
}
}
}
overridAngles = (PM_AdjustAngleForWallRun( ent, ucmd, qtrue )?qtrue:overridAngles);
return overridAngles;
}
void BG_AddPushVecToUcmd( gentity_t *self, usercmd_t *ucmd )
{
vec3_t forward, right, moveDir;
float pushSpeed, fMove, rMove;
if ( !self->client )
{
return;
}
pushSpeed = VectorLengthSquared(self->client->pushVec);
if(!pushSpeed)
{//not being pushed
return;
}
AngleVectors(self->client->ps.viewangles, forward, right, NULL);
VectorScale(forward, ucmd->forwardmove/127.0f * self->client->ps.speed, moveDir);
VectorMA(moveDir, ucmd->rightmove/127.0f * self->client->ps.speed, right, moveDir);
//moveDir is now our intended move velocity
VectorAdd(moveDir, self->client->pushVec, moveDir);
self->client->ps.speed = VectorNormalize(moveDir);
//moveDir is now our intended move velocity plus our push Vector
fMove = 127.0 * DotProduct(forward, moveDir);
rMove = 127.0 * DotProduct(right, moveDir);
ucmd->forwardmove = floor(fMove);//If in the same dir , will be positive
ucmd->rightmove = floor(rMove);//If in the same dir , will be positive
if ( self->client->pushVecTime < level.time )
{
VectorClear( self->client->pushVec );
}
}
void NPC_Accelerate( gentity_t *ent, qboolean fullWalkAcc, qboolean fullRunAcc )
{
if ( !ent->client || !ent->NPC )
{
return;
}
if ( !ent->NPC->stats.acceleration )
{//No acceleration means just start and stop
ent->NPC->currentSpeed = ent->NPC->desiredSpeed;
}
//FIXME: in cinematics always accel/decel?
else if ( ent->NPC->desiredSpeed <= ent->NPC->stats.walkSpeed )
{//Only accelerate if at walkSpeeds
if ( ent->NPC->desiredSpeed > ent->NPC->currentSpeed + ent->NPC->stats.acceleration )
{
//ent->client->ps.friction = 0;
ent->NPC->currentSpeed += ent->NPC->stats.acceleration;
}
else if ( ent->NPC->desiredSpeed > ent->NPC->currentSpeed )
{
//ent->client->ps.friction = 0;
ent->NPC->currentSpeed = ent->NPC->desiredSpeed;
}
else if ( fullWalkAcc && ent->NPC->desiredSpeed < ent->NPC->currentSpeed - ent->NPC->stats.acceleration )
{//decelerate even when walking
ent->NPC->currentSpeed -= ent->NPC->stats.acceleration;
}
else if ( ent->NPC->desiredSpeed < ent->NPC->currentSpeed )
{//stop on a dime
ent->NPC->currentSpeed = ent->NPC->desiredSpeed;
}
}
else// if ( ent->NPC->desiredSpeed > ent->NPC->stats.walkSpeed )
{//Only decelerate if at runSpeeds
if ( fullRunAcc && ent->NPC->desiredSpeed > ent->NPC->currentSpeed + ent->NPC->stats.acceleration )
{//Accelerate to runspeed
//ent->client->ps.friction = 0;
ent->NPC->currentSpeed += ent->NPC->stats.acceleration;
}
else if ( ent->NPC->desiredSpeed > ent->NPC->currentSpeed )
{//accelerate instantly
//ent->client->ps.friction = 0;
ent->NPC->currentSpeed = ent->NPC->desiredSpeed;
}
else if ( fullRunAcc && ent->NPC->desiredSpeed < ent->NPC->currentSpeed - ent->NPC->stats.acceleration )
{
ent->NPC->currentSpeed -= ent->NPC->stats.acceleration;
}
else if ( ent->NPC->desiredSpeed < ent->NPC->currentSpeed )
{
ent->NPC->currentSpeed = ent->NPC->desiredSpeed;
}
}
}
/*
-------------------------
NPC_GetWalkSpeed
-------------------------
*/
static int NPC_GetWalkSpeed( gentity_t *ent )
{
int walkSpeed = 0;
if ( ( ent->client == NULL ) || ( ent->NPC == NULL ) )
return 0;
switch ( ent->client->playerTeam )
{
case TEAM_PLAYER: //To shutup compiler, will add entries later (this is stub code)
default:
walkSpeed = ent->NPC->stats.walkSpeed;
break;
}
return walkSpeed;
}
/*
-------------------------
NPC_GetRunSpeed
-------------------------
*/
#define BORG_RUN_INCR 25
#define SPECIES_RUN_INCR 25
#define STASIS_RUN_INCR 20
#define WARBOT_RUN_INCR 20
static int NPC_GetRunSpeed( gentity_t *ent )
{
int runSpeed = 0;
if ( ( ent->client == NULL ) || ( ent->NPC == NULL ) )
return 0;
// team no longer indicates species/race. Use NPC_class to adjust speed for specific npc types
switch( ent->client->NPC_class)
{
case CLASS_PROBE: // droid cases here to shut-up compiler
case CLASS_GONK:
case CLASS_R2D2:
case CLASS_R5D2:
case CLASS_MARK1:
case CLASS_MARK2:
case CLASS_PROTOCOL:
case CLASS_ATST: // hmm, not really your average droid
case CLASS_MOUSE:
case CLASS_SEEKER:
case CLASS_REMOTE:
runSpeed = ent->NPC->stats.runSpeed;
break;
default:
runSpeed = ent->NPC->stats.runSpeed;
break;
}
return runSpeed;
}
void G_UpdateEmplacedWeaponData( gentity_t *ent )
{
if ( ent && ent->owner && ent->health > 0 )
{
gentity_t *chair = ent->owner;
//take the emplaced gun's waypoint as your own
ent->waypoint = chair->waypoint;
//update the actual origin of the sitter
mdxaBone_t boltMatrix;
vec3_t chairAng = {0, ent->client->ps.viewangles[YAW], 0};
// Getting the seat bolt here
gi.G2API_GetBoltMatrix( chair->ghoul2, chair->playerModel, chair->headBolt,
&boltMatrix, chairAng, chair->currentOrigin, (cg.time?cg.time:level.time),
NULL, chair->s.modelScale );
// Storing ent position, bolt position, and bolt axis
gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, ent->client->ps.origin );
gi.linkentity( ent );
}
}
void ExitEmplacedWeapon( gentity_t *ent )
{
// requesting to unlock from the weapon
int oldWeapon;
// Remove this gun from our inventory
ent->client->ps.stats[STAT_WEAPONS] &= ~( 1 << ent->client->ps.weapon );
// when we lock or unlock from the the gun, we just swap weapons with it
oldWeapon = ent->client->ps.weapon;
ent->client->ps.weapon = ent->owner->s.weapon;
ent->owner->s.weapon = oldWeapon;
extern void ChangeWeapon( gentity_t *ent, int newWeapon );
if ( ent->NPC )
{
ChangeWeapon( ent, ent->client->ps.weapon );
}
else
{
extern void CG_ChangeWeapon( int num );
CG_ChangeWeapon( ent->client->ps.weapon );
if (weaponData[ent->client->ps.weapon].weaponMdl[0])
{
//might be NONE, so check if it has a model
G_CreateG2AttachedWeaponModel( ent, weaponData[ent->client->ps.weapon].weaponMdl );
if ( ent->client->ps.weapon == WP_SABER && cg_saberAutoThird.value )
{
gi.cvar_set( "cg_thirdperson", "1" );
}
else if ( ent->client->ps.weapon != WP_SABER && cg_gunAutoFirst.value )
{
gi.cvar_set( "cg_thirdperson", "0" );
}
}
}
if ( ent->client->ps.weapon == WP_SABER )
{
ent->client->ps.saberActive = ent->owner->alt_fire;
}
// We'll leave the gun pointed in the direction it was last facing, though we'll cut out the pitch
if ( ent->client )
{
VectorCopy( ent->client->ps.viewangles, ent->owner->s.angles );
ent->owner->s.angles[PITCH] = 0;
G_SetAngles( ent->owner, ent->owner->s.angles );
VectorCopy( ent->owner->s.angles, ent->owner->pos1 );
// if we are the player we will have put down a brush that blocks NPCs so that we have a clear spot to get back out.
//gentity_t *place = G_Find( NULL, FOFS(classname), "emp_placeholder" );
if ( ent->health > 0 && ent->owner->nextTrain )
{//he's still alive, and we have a placeholder, so put him back
// reset the players position
VectorCopy( ent->owner->nextTrain->currentOrigin, ent->client->ps.origin );
//reset ent's size to normal
VectorCopy( ent->owner->nextTrain->mins, ent->mins );
VectorCopy( ent->owner->nextTrain->maxs, ent->maxs );
//free the placeholder
G_FreeEntity( ent->owner->nextTrain );
//re-link the ent
gi.linkentity( ent );
}
else if ( ent->health <= 0 )
{
// dead, so give 'em a push out of the chair
vec3_t dir;
AngleVectors( ent->owner->s.angles, NULL, dir, NULL );
if ( rand() & 1 )
{
VectorScale( dir, -1, dir );
}
VectorMA( ent->client->ps.velocity, 75, dir, ent->client->ps.velocity );
}
}
// gi.G2API_DetachG2Model( &ent->ghoul2[ent->playerModel] );
ent->s.eFlags &= ~EF_LOCKED_TO_WEAPON;
ent->client->ps.eFlags &= ~EF_LOCKED_TO_WEAPON;
ent->owner->noDamageTeam = TEAM_FREE;
ent->owner->svFlags &= ~SVF_NONNPC_ENEMY;
ent->owner->delay = level.time;
ent->owner->activator = NULL;
if ( !ent->NPC )
{
// by keeping the owner, a dead npc can be pushed out of the chair without colliding with it
ent->owner = NULL;
}
}
void RunEmplacedWeapon( gentity_t *ent, usercmd_t **ucmd )
{
if (( (*ucmd)->buttons & BUTTON_USE || /*(*ucmd)->forwardmove < 0 ||*/ (*ucmd)->upmove > 0 ) && ent->owner && ent->owner->delay + 500 < level.time )
{
ent->owner->s.loopSound = 0;
ExitEmplacedWeapon( ent );
(*ucmd)->buttons &= ~BUTTON_USE;
G_Sound( ent, G_SoundIndex( "sound/weapons/emplaced/emplaced_dismount.mp3" ));
}
else
{
// this is a crappy way to put sounds on a moving emplaced gun....
/* if ( ent->owner )
{
if ( !VectorCompare( ent->owner->pos3, ent->owner->movedir ))
{
ent->owner->s.loopSound = G_SoundIndex( "sound/weapons/emplaced/emplaced_move_lp.wav" );
ent->owner->fly_sound_debounce_time = level.time;
}
else
{
if ( ent->owner->fly_sound_debounce_time + 100 <= level.time )
{
ent->owner->s.loopSound = 0;
}
}
VectorCopy( ent->owner->pos3, ent->owner->movedir );
}
*/
// don't allow movement, weapon switching, and most kinds of button presses
(*ucmd)->forwardmove = 0;
(*ucmd)->rightmove = 0;
(*ucmd)->upmove = 0;
(*ucmd)->buttons &= (BUTTON_ATTACK|BUTTON_ALT_ATTACK);
(*ucmd)->weapon = ent->client->ps.weapon; //WP_EMPLACED_GUN;
if ( ent->health <= 0 )
{
ExitEmplacedWeapon( ent );
}
}
}
// yes... so stop skipping...
void G_StopCinematicSkip( void )
{
gi.cvar_set("skippingCinematic", "0");
gi.cvar_set("timescale", "1");
}
void G_StartCinematicSkip( void )
{
if (cinematicSkipScript[0])
{
ICARUS_RunScript( &g_entities[0], va( "%s/%s", Q3_SCRIPT_DIR, cinematicSkipScript ) );
memset( cinematicSkipScript, 0, sizeof( cinematicSkipScript ) );
gi.cvar_set("skippingCinematic", "1");
gi.cvar_set("timescale", "100");
}
else
{
// no... so start skipping...
gi.cvar_set("skippingCinematic", "1");
gi.cvar_set("timescale", "100");
}
}
void G_CheckClientIdle( gentity_t *ent, usercmd_t *ucmd )
{
if ( !ent || !ent->client || ent->health <= 0 )
{
return;
}
if ( !ent->s.number && ( !cg.renderingThirdPerson || cg.zoomMode ) )
{
if ( ent->client->idleTime < level.time )
{
ent->client->idleTime = level.time;
}
return;
}
if ( !VectorCompare( vec3_origin, ent->client->ps.velocity )
|| ucmd->buttons || ucmd->forwardmove || ucmd->rightmove || ucmd->upmove
|| !PM_StandingAnim( ent->client->ps.legsAnim )
|| ent->enemy
|| ent->client->ps.legsAnimTimer
|| ent->client->ps.torsoAnimTimer )
{//FIXME: also check for turning?
if ( !VectorCompare( vec3_origin, ent->client->ps.velocity )
|| ucmd->buttons || ucmd->forwardmove || ucmd->rightmove || ucmd->upmove
|| ent->enemy )
{
//if in an idle, break out
switch ( ent->client->ps.legsAnim )
{
case BOTH_STAND1IDLE1:
case BOTH_STAND2IDLE1:
case BOTH_STAND2IDLE2:
case BOTH_STAND3IDLE1:
case BOTH_STAND4IDLE1:
ent->client->ps.legsAnimTimer = 0;
break;
}
switch ( ent->client->ps.torsoAnim )
{
case BOTH_STAND1IDLE1:
case BOTH_STAND2IDLE1:
case BOTH_STAND2IDLE2:
case BOTH_STAND3IDLE1:
case BOTH_STAND4IDLE1:
ent->client->ps.torsoAnimTimer = 0;
break;
}
}
//
if ( ent->client->idleTime < level.time )
{
ent->client->idleTime = level.time;
}
}
else if ( level.time - ent->client->idleTime > 5000 )
{//been idle for 5 seconds
int idleAnim = -1;
switch ( ent->client->ps.legsAnim )
{
case BOTH_STAND1:
idleAnim = BOTH_STAND1IDLE1;
break;
case BOTH_STAND2:
idleAnim = Q_irand(BOTH_STAND2IDLE1,BOTH_STAND2IDLE2);
break;
case BOTH_STAND3:
idleAnim = BOTH_STAND3IDLE1;
break;
case BOTH_STAND4:
idleAnim = BOTH_STAND4IDLE1;
break;
}
if ( idleAnim != -1 && PM_HasAnimation( ent, idleAnim ) )
{
NPC_SetAnim( ent, SETANIM_BOTH, idleAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
//don't idle again after this anim for a while
ent->client->idleTime = level.time + PM_AnimLength( ent->client->clientInfo.animFileIndex, (animNumber_t)idleAnim ) + Q_irand( 0, 2000 );
}
}
}
void G_CheckMovingLoopingSounds( gentity_t *ent, usercmd_t *ucmd )
{
if ( ent->client )
{
if ( (ent->NPC&&!VectorCompare( vec3_origin, ent->client->ps.moveDir ))//moving using moveDir
|| ucmd->forwardmove || ucmd->rightmove//moving using ucmds
|| (ucmd->upmove&&FlyingCreature( ent ))//flier using ucmds to move
|| (FlyingCreature( ent )&&!VectorCompare( vec3_origin, ent->client->ps.velocity )&&ent->health>0))//flier using velocity to move
{
switch( ent->client->NPC_class )
{
case CLASS_R2D2:
ent->s.loopSound = G_SoundIndex( "sound/chars/r2d2/misc/r2_move_lp.wav" );
break;
case CLASS_R5D2:
ent->s.loopSound = G_SoundIndex( "sound/chars/r2d2/misc/r2_move_lp2.wav" );
break;
case CLASS_MARK2:
ent->s.loopSound = G_SoundIndex( "sound/chars/mark2/misc/mark2_move_lp" );
break;
case CLASS_MOUSE:
ent->s.loopSound = G_SoundIndex( "sound/chars/mouse/misc/mouse_lp" );
break;
case CLASS_PROBE:
ent->s.loopSound = G_SoundIndex( "sound/chars/probe/misc/probedroidloop" );
default:
break;
}
}
else
{//not moving under your own control, stop loopSound
if ( ent->client->NPC_class == CLASS_R2D2 || ent->client->NPC_class == CLASS_R5D2
|| ent->client->NPC_class == CLASS_MARK2 || ent->client->NPC_class == CLASS_MOUSE
|| ent->client->NPC_class == CLASS_PROBE )
{
ent->s.loopSound = 0;
}
}
}
}
/*
==============
ClientThink
This will be called once for each client frame, which will
usually be a couple times for each server frame on fast clients.
==============
*/
extern int G_FindLocalInterestPoint( gentity_t *self );
void ClientThink_real( gentity_t *ent, usercmd_t *ucmd )
{
gclient_t *client;
pmove_t pm;
vec3_t oldOrigin;
int oldEventSequence;
int msec;
qboolean inSpinFlipAttack = PM_AdjustAnglesForSpinningFlip( ent, ucmd, qfalse );
qboolean controlledByPlayer = qfalse;
//Don't let the player do anything if in a camera
if ( ent->s.number == 0 )
{
extern cvar_t *g_skippingcin;
if ( ent->s.eFlags & EF_LOCKED_TO_WEAPON )
{
G_UpdateEmplacedWeaponData( ent );
RunEmplacedWeapon( ent, &ucmd );
}
if ( ent->client->ps.saberLockTime > level.time && ent->client->ps.saberLockEnemy != ENTITYNUM_NONE )
{
NPC_SetLookTarget( ent, ent->client->ps.saberLockEnemy, level.time+1000 );
}
if ( ent->client->renderInfo.lookTargetClearTime < level.time //NOTE: here this is used as a debounce, not an actual timer
&& ent->health > 0 //must be alive
&& (!ent->enemy || ent->client->ps.saberMove != LS_A_BACKSTAB) )//don't update if in backstab unless don't currently have an enemy
{//NOTE: doesn't keep updating to nearest enemy once you're dead
int newLookTarget;
if ( !G_ValidateLookEnemy( ent, ent->enemy ) )
{
ent->enemy = NULL;
}
//FIXME: make this a little prescient?
G_ChooseLookEnemy( ent, ucmd );
if ( ent->enemy )
{//target
newLookTarget = ent->enemy->s.number;
//so we don't change our minds in the next 1 second
ent->client->renderInfo.lookTargetClearTime = level.time+1000;
ent->client->renderInfo.lookMode = LM_ENT;
}
else
{//no target
//FIXME: what about sightalerts and missiles?
newLookTarget = ENTITYNUM_NONE;
newLookTarget = G_FindLocalInterestPoint( ent );
if ( newLookTarget != ENTITYNUM_NONE )
{//found something of interest
ent->client->renderInfo.lookMode = LM_INTEREST;
}
else
{//okay, no interesting things and no enemies, so look for items
newLookTarget = G_FindLookItem( ent );
ent->client->renderInfo.lookMode = LM_ENT;
}
}
if ( ent->client->renderInfo.lookTarget != newLookTarget )
{//transitioning
NPC_SetLookTarget( ent, newLookTarget, level.time+1000 );
}
}
if ( in_camera )
{
// watch the code here, you MUST "return" within this IF(), *unless* you're stopping the cinematic skip.
//
if ( ClientCinematicThink(ent->client) )
{
if (g_skippingcin->integer) // already doing cinematic skip?
{// yes... so stop skipping...
G_StopCinematicSkip();
}
else
{// no... so start skipping...
G_StartCinematicSkip();
return;
}
}
else
{
return;
}
}
else if ( ent->client->ps.vehicleModel != 0 && ent->health > 0 )
{
float speed;
speed = VectorLength( ent->client->ps.velocity );
if ( speed && ent->client->ps.groundEntityNum == ENTITYNUM_NONE )
{
int diff = AngleNormalize180(SHORT2ANGLE(ucmd->angles[YAW]+ent->client->ps.delta_angles[YAW]) - floor(ent->client->ps.viewangles[YAW]));
int slide = floor(((float)(diff))/120.0f*-127.0f);
if ( (slide > 0 && ucmd->rightmove >= 0) || ((slide < 0 && ucmd->rightmove <= 0)) )
{//note: don't want these to conflict right now because that seems to feel really weird
//gi.Printf( "slide %i, diff %i, yaw %i\n", slide, diff, ucmd->angles[YAW] );
ucmd->rightmove += slide;
}
if ( ucmd->rightmove > 64 )
{
ucmd->rightmove = 64;
}
else if ( ucmd->rightmove < -64 )
{
ucmd->rightmove = -64;
}
}
else
{
ucmd->rightmove = 0;
ucmd->angles[PITCH] = 0;
ucmd->angles[YAW] = ANGLE2SHORT( ent->client->ps.viewangles[YAW] ) - ent->client->ps.delta_angles[YAW];
}
}
else
{
if ( g_skippingcin->integer )
{//We're skipping the cinematic and it's over now
gi.cvar_set("timescale", "1");
gi.cvar_set("skippingCinematic", "0");
}
if ( ent->client->ps.pm_type == PM_DEAD && cg.missionStatusDeadTime < level.time )
{//mission status screen is up because player is dead, stop all scripts
stop_icarus = qtrue;
}
}
// // Don't allow the player to adjust the pitch when they are in third person overhead cam.
//extern vmCvar_t cg_thirdPerson;
// if ( cg_thirdPerson.integer == 2 )
// {
// ucmd->angles[PITCH] = 0;
// }
if ( cg.zoomMode == 2 )
{
// Any kind of movement when the player is NOT ducked when the disruptor gun is zoomed will cause us to auto-magically un-zoom
if ( ( (ucmd->forwardmove||ucmd->rightmove)
&& ucmd->upmove >= 0 //crouching-moving is ok
&& !(ucmd->buttons&BUTTON_USE)/*leaning is ok*/
)
|| ucmd->upmove > 0 //jumping not allowed
)
{
// already zooming, so must be wanting to turn it off
G_Sound( ent, G_SoundIndex( "sound/weapons/disruptor/zoomend.wav" ));
cg.zoomMode = 0;
cg.zoomTime = cg.time;
cg.zoomLocked = qfalse;
}
}
if ( (player_locked || (ent->client->ps.eFlags&EF_FORCE_GRIPPED)) && ent->client->ps.pm_type < PM_DEAD ) // unless dead
{//lock out player control
if ( !player_locked )
{
VectorClearM( ucmd->angles );
}
ucmd->forwardmove = 0;
ucmd->rightmove = 0;
ucmd->buttons = 0;
ucmd->upmove = 0;
PM_AdjustAnglesToGripper( ent, ucmd );
}
if ( ent->client->ps.leanofs )
{//no shooting while leaning
ucmd->buttons &= ~BUTTON_ATTACK;
if ( ent->client->ps.weapon != WP_DISRUPTOR )
{//can still zoom around corners
ucmd->buttons &= ~BUTTON_ALT_ATTACK;
}
}
}
else
{
if ( ent->s.eFlags & EF_LOCKED_TO_WEAPON )
{
G_UpdateEmplacedWeaponData( ent );
}
if ( player && player->client && player->client->ps.viewEntity == ent->s.number )
{
controlledByPlayer = qtrue;
int sav_weapon = ucmd->weapon;
memcpy( ucmd, &player->client->usercmd, sizeof( usercmd_t ) );
ucmd->weapon = sav_weapon;
ent->client->usercmd = *ucmd;
}
G_NPCMunroMatchPlayerWeapon( ent );
}
if ( ent->client )
{
if ( ent->client->NPC_class == CLASS_GONK ||
ent->client->NPC_class == CLASS_MOUSE ||
ent->client->NPC_class == CLASS_R2D2 ||
ent->client->NPC_class == CLASS_R5D2 )
{//no jumping or strafing in these guys
ucmd->upmove = ucmd->rightmove = 0;
}
else if ( ent->client->NPC_class == CLASS_ATST )
{//no jumping in atst
if (ent->client->ps.pm_type != PM_NOCLIP)
{
ucmd->upmove = 0;
}
if ( ent->client->ps.groundEntityNum != ENTITYNUM_NONE )
{//ATST crushed anything underneath it
gentity_t *under = &g_entities[ent->client->ps.groundEntityNum];
if ( under && under->health && under->takedamage )
{
vec3_t down = {0,0,-1};
//FIXME: we'll be doing traces down from each foot, so we'll have a real impact origin
G_Damage( under, ent, ent, down, under->currentOrigin, 100, 0, MOD_CRUSH );
}
//so they know to run like hell when I get close
//FIXME: project this in the direction I'm moving?
if ( !Q_irand( 0, 10 ) )
{//not so often...
AddSoundEvent( ent, ent->currentOrigin, ent->maxs[1]*5, AEL_DANGER );
AddSightEvent( ent, ent->currentOrigin, ent->maxs[1]*5, AEL_DANGER, 100 );
}
}
}
else if ( ent->client->ps.groundEntityNum < ENTITYNUM_WORLD && !ent->client->ps.forceJumpCharge )
{//standing on an entity and not currently force jumping
gentity_t *groundEnt = &g_entities[ent->client->ps.groundEntityNum];
if ( groundEnt &&
groundEnt->client &&
groundEnt->client->ps.groundEntityNum != ENTITYNUM_NONE &&
groundEnt->health > 0 &&
!PM_InRoll( &groundEnt->client->ps )
&& !(groundEnt->client->ps.eFlags&EF_LOCKED_TO_WEAPON)
&& !inSpinFlipAttack )
{//landed on a live client who is on the ground, jump off them and knock them down
if ( ent->health > 0 )
{
if ( !PM_InRoll( &ent->client->ps )
&& !PM_FlippingAnim( ent->client->ps.legsAnim ) )
{
if ( ent->s.number && ent->s.weapon == WP_SABER )
{
ent->client->ps.forceJumpCharge = 320;//FIXME: calc this intelligently?
}
else if ( !ucmd->upmove )
{//if not ducking (which should cause a roll), then jump
ucmd->upmove = 127;
}
if ( !ucmd->forwardmove && !ucmd->rightmove )
{// If not moving, don't want to jump straight up
//FIXME: trace for clear di?
if ( !Q_irand( 0, 3 ) )
{
ucmd->forwardmove = 127;
}
else if ( !Q_irand( 0, 3 ) )
{
ucmd->forwardmove = -127;
}
else if ( !Q_irand( 0, 1 ) )
{
ucmd->rightmove = 127;
}
else
{
ucmd->rightmove = -127;
}
}
if ( !ent->s.number && ucmd->upmove < 0 )
{//player who should roll- force it
int rollAnim = BOTH_ROLL_F;
if ( ucmd->forwardmove >= 0 )
{
rollAnim = BOTH_ROLL_F;
}
else if ( ucmd->forwardmove < 0 )
{
rollAnim = BOTH_ROLL_B;
}
else if ( ucmd->rightmove > 0 )
{
rollAnim = BOTH_ROLL_R;
}
else if ( ucmd->rightmove < 0 )
{
rollAnim = BOTH_ROLL_L;
}
NPC_SetAnim(ent,SETANIM_BOTH,rollAnim,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD);
G_AddEvent( ent, EV_ROLL, 0 );
ent->client->ps.saberMove = LS_NONE;
}
}
}
else
{//a corpse? Shit
//Hmm, corpses should probably *always* knockdown...
ent->clipmask &= ~CONTENTS_BODY;
}
//FIXME: need impact sound event
GEntity_PainFunc( groundEnt, ent, ent, groundEnt->currentOrigin, 0, MOD_CRUSH );
if ( groundEnt->client->NPC_class == CLASS_DESANN && ent->client->NPC_class != CLASS_LUKE )
{//can't knock down desann unless you're luke
//FIXME: should he smack you away like Galak Mech?
}
else if (
( ( (groundEnt->s.number&&(groundEnt->s.weapon!=WP_SABER||!groundEnt->NPC||groundEnt->NPC->rank<Q_irand(RANK_CIVILIAN,RANK_CAPTAIN+1))) //an NPC who is either not a saber user or passed the rank-based probability test
|| ((!ent->s.number||G_ControlledByPlayer(groundEnt)) && !Q_irand( 0, 3 )&&cg.renderingThirdPerson&&!cg.zoomMode) )//or a player in third person, 25% of the time
&& groundEnt->client->playerTeam != ent->client->playerTeam ) //and not on the same team
|| ent->client->NPC_class == CLASS_DESANN )//desann always knocks people down
{
int knockAnim = BOTH_KNOCKDOWN1;
if ( PM_CrouchAnim( groundEnt->client->ps.legsAnim ) )
{//knockdown from crouch
knockAnim = BOTH_KNOCKDOWN4;
}
else
{
vec3_t gEFwd, gEAngles = {0,groundEnt->client->ps.viewangles[YAW],0};
AngleVectors( gEAngles, gEFwd, NULL, NULL );
if ( DotProduct( ent->client->ps.velocity, gEFwd ) > 50 )
{//pushing him forward
knockAnim = BOTH_KNOCKDOWN3;
}
}
NPC_SetAnim( groundEnt, SETANIM_BOTH, knockAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD );
}
}
}
}
client = ent->client;
// mark the time, so the connection sprite can be removed
client->lastCmdTime = level.time;
client->pers.lastCommand = *ucmd;
// sanity check the command time to prevent speedup cheating
if ( ucmd->serverTime > level.time + 200 )
{
ucmd->serverTime = level.time + 200;
}
if ( ucmd->serverTime < level.time - 1000 )
{
ucmd->serverTime = level.time - 1000;
}
msec = ucmd->serverTime - client->ps.commandTime;
if ( msec < 1 )
{
msec = 1;
}
if ( msec > 200 )
{
msec = 200;
}
// check for inactivity timer, but never drop the local client of a non-dedicated server
if ( !ClientInactivityTimer( client ) )
return;
if ( client->noclip )
{
client->ps.pm_type = PM_NOCLIP;
}
else if ( client->ps.stats[STAT_HEALTH] <= 0 )
{
client->ps.pm_type = PM_DEAD;
}
else
{
client->ps.pm_type = PM_NORMAL;
}
//FIXME: if global gravity changes this should update everyone's personal gravity...
if ( !(ent->svFlags & SVF_CUSTOM_GRAVITY) )
{
client->ps.gravity = g_gravity->value;
}
// set speed
if ( ent->NPC != NULL )
{//we don't actually scale the ucmd, we use actual speeds
if ( ent->NPC->combatMove == qfalse )
{
if ( !(ucmd->buttons & BUTTON_USE) )
{//Not leaning
qboolean Flying = (qboolean)(ucmd->upmove && ent->NPC->stats.moveType == MT_FLYSWIM);
qboolean Climbing = (qboolean)(ucmd->upmove && (ent->watertype & CONTENTS_LADDER));
client->ps.friction = 6;
if ( ucmd->forwardmove || ucmd->rightmove || Flying )
{
//if ( ent->NPC->behaviorState != BS_FORMATION )
{//In - Formation NPCs set thier desiredSpeed themselves
if ( ucmd->buttons & BUTTON_WALKING )
{
ent->NPC->desiredSpeed = NPC_GetWalkSpeed( ent );//ent->NPC->stats.walkSpeed;
}
else//running
{
ent->NPC->desiredSpeed = NPC_GetRunSpeed( ent );//ent->NPC->stats.runSpeed;
}
if ( ent->NPC->currentSpeed >= 80 && !controlledByPlayer )
{//At higher speeds, need to slow down close to stuff
//Slow down as you approach your goal
if ( ent->NPC->distToGoal < SLOWDOWN_DIST && !(ent->NPC->aiFlags&NPCAI_NO_SLOWDOWN) )//128
{
if ( ent->NPC->desiredSpeed > MIN_NPC_SPEED )
{
float slowdownSpeed = ((float)ent->NPC->desiredSpeed) * ent->NPC->distToGoal / SLOWDOWN_DIST;
ent->NPC->desiredSpeed = ceil(slowdownSpeed);
if ( ent->NPC->desiredSpeed < MIN_NPC_SPEED )
{//don't slow down too much
ent->NPC->desiredSpeed = MIN_NPC_SPEED;
}
}
}
}
}
}
else if ( Climbing )
{
ent->NPC->desiredSpeed = ent->NPC->stats.walkSpeed;
}
else
{//We want to stop
ent->NPC->desiredSpeed = 0;
}
NPC_Accelerate( ent, qfalse, qfalse );
if ( ent->NPC->currentSpeed <= 24 && ent->NPC->desiredSpeed < ent->NPC->currentSpeed )
{//No-one walks this slow
client->ps.speed = ent->NPC->currentSpeed = 0;//Full stop
ucmd->forwardmove = 0;
ucmd->rightmove = 0;
}
else
{
if ( ent->NPC->currentSpeed <= ent->NPC->stats.walkSpeed )
{//Play the walkanim
ucmd->buttons |= BUTTON_WALKING;
}
else
{
ucmd->buttons &= ~BUTTON_WALKING;
}
if ( ent->NPC->currentSpeed > 0 )
{//We should be moving
if ( Climbing || Flying )
{
if ( !ucmd->upmove )
{//We need to force them to take a couple more steps until stopped
ucmd->upmove = ent->NPC->last_ucmd.upmove;//was last_upmove;
}
}
else if ( !ucmd->forwardmove && !ucmd->rightmove )
{//We need to force them to take a couple more steps until stopped
ucmd->forwardmove = ent->NPC->last_ucmd.forwardmove;//was last_forwardmove;
ucmd->rightmove = ent->NPC->last_ucmd.rightmove;//was last_rightmove;
}
}
client->ps.speed = ent->NPC->currentSpeed;
if ( player && player->client && player->client->ps.viewEntity == ent->s.number )
{
}
else
{
//Slow down on turns - don't orbit!!!
float turndelta = 0;
// if the NPC is locked into a Yaw, we want to check the lockedDesiredYaw...otherwise the NPC can't walk backwards, because it always thinks it trying to turn according to desiredYaw
if( client->renderInfo.renderFlags & RF_LOCKEDANGLE ) // yeah I know the RF_ flag is a pretty ugly hack...
{
turndelta = (180 - fabs( AngleDelta( ent->currentAngles[YAW], ent->NPC->lockedDesiredYaw ) ))/180;
}
else
{
turndelta = (180 - fabs( AngleDelta( ent->currentAngles[YAW], ent->NPC->desiredYaw ) ))/180;
}
if ( turndelta < 0.75f )
{
client->ps.speed = 0;
}
else if ( ent->NPC->distToGoal < 100 && turndelta < 1.0 )
{//Turn is greater than 45 degrees or closer than 100 to goal
client->ps.speed = floor(((float)(client->ps.speed))*turndelta);
}
}
}
}
}
else
{
ent->NPC->desiredSpeed = ( ucmd->buttons & BUTTON_WALKING ) ? NPC_GetWalkSpeed( ent ) : NPC_GetRunSpeed( ent );
client->ps.speed = ent->NPC->desiredSpeed;
}
}
else
{//Client sets ucmds and such for speed alterations
if ( ent->client->ps.vehicleModel != 0 && ent->health > 0 )
{
if ( client->ps.speed || client->ps.groundEntityNum == ENTITYNUM_NONE || ucmd->forwardmove > 0 || ucmd->upmove > 0)
{
if ( ucmd->forwardmove > 0 )
{
client->ps.speed += 20;
}
else if ( ucmd->forwardmove < 0 )
{
if ( client->ps.speed > 800 )
{
client->ps.speed -= 20;
}
else
{
client->ps.speed -= 5;
}
}
else
{//accelerate to cruising speed only, otherwise, just coast
if ( client->ps.speed < 800 )
{
client->ps.speed += 10;
if ( client->ps.speed > 800 )
{
client->ps.speed = 800;
}
}
}
ucmd->forwardmove = 127;
}
else
{
if ( ucmd->forwardmove < 0 )
{
ucmd->forwardmove = 0;
}
if ( ucmd->upmove < 0 )
{
ucmd->upmove = 0;
}
ucmd->rightmove = 0;
}
if ( client->ps.speed > 3000 )
{
client->ps.speed = 3000;
}
else if ( client->ps.speed < 0 )
{
client->ps.speed = 0;
}
if ( client->ps.speed < 800 )
{
client->ps.gravity = (800 - client->ps.speed)/4;
}
else
{
client->ps.gravity = 0;
}
}
else
{
client->ps.speed = g_speed->value;//default is 320
/*if ( !ent->s.number && ent->painDebounceTime>level.time )
{
client->ps.speed *= 0.25f;
}
else */if ( PM_SaberInAttack( ent->client->ps.saberMove ) && ucmd->forwardmove < 0 )
{//if running backwards while attacking, don't run as fast.
switch( client->ps.saberAnimLevel )
{
case FORCE_LEVEL_1:
client->ps.speed *= 0.75f;
break;
case FORCE_LEVEL_2:
client->ps.speed *= 0.60f;
break;
case FORCE_LEVEL_3:
client->ps.speed *= 0.45f;
break;
}
if ( g_saberMoveSpeed->value != 1.0f )
{
client->ps.speed *= g_saberMoveSpeed->value;
}
}
else if ( PM_SpinningSaberAnim( client->ps.legsAnim ) )
{
client->ps.speed *= 0.5f;
if ( g_saberMoveSpeed->value != 1.0f )
{
client->ps.speed *= g_saberMoveSpeed->value;
}
}
else if ( client->ps.weapon == WP_SABER && ( ucmd->buttons & BUTTON_ATTACK ) )
{//if attacking with saber while running, drop your speed
//FIXME: should be weaponTime? Or in certain anims?
switch( client->ps.saberAnimLevel )
{
case FORCE_LEVEL_2:
client->ps.speed *= 0.85f;
break;
case FORCE_LEVEL_3:
client->ps.speed *= 0.70f;
break;
}
if ( g_saberMoveSpeed->value != 1.0f )
{
client->ps.speed *= g_saberMoveSpeed->value;
}
}
}
}
if ( client->NPC_class == CLASS_ATST && client->ps.legsAnim == BOTH_RUN1START )
{//HACK: when starting to move as atst, ramp up speed
//float animLength = PM_AnimLength( client->clientInfo.animFileIndex, (animNumber_t)client->ps.legsAnim);
//client->ps.speed *= ( animLength - client->ps.legsAnimTimer)/animLength;
if ( client->ps.legsAnimTimer > 100 )
{
client->ps.speed = 0;
}
}
//Apply forced movement
if ( client->forced_forwardmove )
{
ucmd->forwardmove = client->forced_forwardmove;
if ( !client->ps.speed )
{
if ( ent->NPC != NULL )
{
client->ps.speed = ent->NPC->stats.runSpeed;
}
else
{
client->ps.speed = g_speed->value;//default is 320
}
}
}
if ( client->forced_rightmove )
{
ucmd->rightmove = client->forced_rightmove;
if ( !client->ps.speed )
{
if ( ent->NPC != NULL )
{
client->ps.speed = ent->NPC->stats.runSpeed;
}
else
{
client->ps.speed = g_speed->value;//default is 320
}
}
}
if ( ucmd->forwardmove < 0 && !(ucmd->buttons&BUTTON_WALKING) && client->ps.groundEntityNum != ENTITYNUM_NONE )
{//running backwards is slower than running forwards
client->ps.speed *= 0.75;
}
//FIXME: need to do this before check to avoid walls and cliffs (or just cliffs?)
BG_AddPushVecToUcmd( ent, ucmd );
G_CheckClampUcmd( ent, ucmd );
if ( (ucmd->buttons&BUTTON_BLOCKING) && !g_saberAutoBlocking->integer )
{//blocking with saber
ent->client->ps.saberBlockingTime = level.time + FRAMETIME;
}
WP_ForcePowersUpdate( ent, ucmd );
//if we have the saber in hand, check for starting a block to reflect shots
WP_SaberStartMissileBlockCheck( ent, ucmd );
// Update the position of the saber, and check to see if we're throwing it
if ( client->ps.saberEntityNum != ENTITYNUM_NONE )
{
int updates = 1;
if ( ent->NPC )
{
updates = 3;//simulate player update rate?
}
for ( int update = 0; update < updates; update++ )
{
WP_SaberUpdate( ent, ucmd );
}
}
//NEED to do this every frame, since these overrides do not go into the save/load data
if ( ent->client->ps.vehicleModel != 0 )
{
cg.overrides.active |= (CG_OVERRIDE_3RD_PERSON_RNG|CG_OVERRIDE_FOV);
cg.overrides.thirdPersonRange = 240;
cg.overrides.fov = 100;
}
else if ( client->ps.eFlags&EF_IN_ATST )
{
cg.overrides.active |= (CG_OVERRIDE_3RD_PERSON_RNG|CG_OVERRIDE_3RD_PERSON_POF|CG_OVERRIDE_3RD_PERSON_VOF);
cg.overrides.thirdPersonRange = 240;
if ( cg_thirdPersonAutoAlpha.integer )
{
if ( ent->health > 0 && ent->client->ps.viewangles[PITCH] < 15 && ent->client->ps.viewangles[PITCH] > 0 )
{
cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_APH;
if ( cg.overrides.thirdPersonAlpha > 0.525f )
{
cg.overrides.thirdPersonAlpha -= 0.025f;
}
else if ( cg.overrides.thirdPersonAlpha > 0.5f )
{
cg.overrides.thirdPersonAlpha = 0.5f;
}
}
else if ( cg.overrides.active&CG_OVERRIDE_3RD_PERSON_APH )
{
if ( cg.overrides.thirdPersonAlpha > cg_thirdPersonAlpha.value )
{
cg.overrides.active &= ~CG_OVERRIDE_3RD_PERSON_APH;
}
else if ( cg.overrides.thirdPersonAlpha < cg_thirdPersonAlpha.value-0.1f )
{
cg.overrides.thirdPersonAlpha += 0.1f;
}
else if ( cg.overrides.thirdPersonAlpha < cg_thirdPersonAlpha.value )
{
cg.overrides.thirdPersonAlpha = cg_thirdPersonAlpha.value;
cg.overrides.active &= ~CG_OVERRIDE_3RD_PERSON_APH;
}
}
}
if ( ent->client->ps.viewangles[PITCH] > 0 )
{
cg.overrides.thirdPersonPitchOffset = ent->client->ps.viewangles[PITCH]*-0.75;
cg.overrides.thirdPersonVertOffset = 300+ent->client->ps.viewangles[PITCH]*-10;
if ( cg.overrides.thirdPersonVertOffset < 0 )
{
cg.overrides.thirdPersonVertOffset = 0;
}
}
else if ( ent->client->ps.viewangles[PITCH] < 0 )
{
cg.overrides.thirdPersonPitchOffset = ent->client->ps.viewangles[PITCH]*-0.75;
cg.overrides.thirdPersonVertOffset = 300+ent->client->ps.viewangles[PITCH]*-5;
if ( cg.overrides.thirdPersonVertOffset > 300 )
{
cg.overrides.thirdPersonVertOffset = 300;
}
}
else
{
cg.overrides.thirdPersonPitchOffset = 0;
cg.overrides.thirdPersonVertOffset = 200;
}
}
//play/stop any looping sounds tied to controlled movement
G_CheckMovingLoopingSounds( ent, ucmd );
//remember your last angles
VectorCopy ( ent->client->ps.viewangles, ent->lastAngles );
// set up for pmove
oldEventSequence = client->ps.eventSequence;
memset( &pm, 0, sizeof(pm) );
pm.gent = ent;
pm.ps = &client->ps;
pm.cmd = *ucmd;
// pm.tracemask = MASK_PLAYERSOLID; // used differently for navgen
pm.tracemask = ent->clipmask;
pm.trace = gi.trace;
pm.pointcontents = gi.pointcontents;
pm.debugLevel = g_debugMove->integer;
pm.noFootsteps = qfalse;//( g_dmflags->integer & DF_NO_FOOTSTEPS ) > 0;
VectorCopy( client->ps.origin, oldOrigin );
// perform a pmove
Pmove( &pm );
// save results of pmove
if ( ent->client->ps.eventSequence != oldEventSequence )
{
ent->eventTime = level.time;
{
int seq;
seq = (ent->client->ps.eventSequence-1) & (MAX_PS_EVENTS-1);
ent->s.event = ent->client->ps.events[ seq ] | ( ( ent->client->ps.eventSequence & 3 ) << 8 );
ent->s.eventParm = ent->client->ps.eventParms[ seq ];
}
}
PlayerStateToEntityState( &ent->client->ps, &ent->s );
VectorCopy ( ent->currentOrigin, ent->lastOrigin );
#if 1
// use the precise origin for linking
VectorCopy( ent->client->ps.origin, ent->currentOrigin );
#else
//We don't use prediction anymore, so screw this
// use the snapped origin for linking so it matches client predicted versions
VectorCopy( ent->s.pos.trBase, ent->currentOrigin );
#endif
//Had to leave this in, some legacy code must still be using s.angles
//Shouldn't interfere with interpolation of angles, should it?
VectorCopy( ent->client->ps.viewangles, ent->currentAngles );
VectorCopy( pm.mins, ent->mins );
VectorCopy( pm.maxs, ent->maxs );
ent->waterlevel = pm.waterlevel;
ent->watertype = pm.watertype;
VectorCopyM( ucmd->angles, client->pers.cmd_angles );
// execute client events
ClientEvents( ent, oldEventSequence );
if ( pm.useEvent )
{
//TODO: Use
TryUse( ent );
}
// link entity now, after any personal teleporters have been used
gi.linkentity( ent );
ent->client->hiddenDist = 0;
if ( !ent->client->noclip )
{
G_TouchTriggersLerped( ent );
}
// touch other objects
ClientImpacts( ent, &pm );
// swap and latch button actions
client->oldbuttons = client->buttons;
client->buttons = ucmd->buttons;
client->latched_buttons |= client->buttons & ~client->oldbuttons;
// check for respawning
if ( client->ps.stats[STAT_HEALTH] <= 0 )
{
// wait for the attack button to be pressed
if ( ent->NPC == NULL && level.time > client->respawnTime )
{
// don't allow respawn if they are still flying through the
// air, unless 10 extra seconds have passed, meaning something
// strange is going on, like the corpse is caught in a wind tunnel
/*
if ( level.time < client->respawnTime + 10000 )
{
if ( client->ps.groundEntityNum == ENTITYNUM_NONE )
{
return;
}
}
*/
// pressing attack or use is the normal respawn method
if ( ucmd->buttons & ( BUTTON_ATTACK ) )
{
respawn( ent );
// gi.SendConsoleCommand( va("disconnect;wait;wait;wait;wait;wait;wait;devmap %s\n",level.mapname) );
}
}
if ( ent
&& !ent->s.number
&& ent->enemy
&& ent->enemy != ent
&& ent->enemy->s.number < ENTITYNUM_WORLD
&& ent->enemy->inuse
&& !(cg.overrides.active&CG_OVERRIDE_3RD_PERSON_ANG) )
{//keep facing enemy
vec3_t deadDir;
float deadYaw;
VectorSubtract( ent->enemy->currentOrigin, ent->currentOrigin, deadDir );
deadYaw = AngleNormalize180( vectoyaw ( deadDir ) );
if ( deadYaw > ent->client->ps.stats[STAT_DEAD_YAW] + 1 )
{
ent->client->ps.stats[STAT_DEAD_YAW]++;
}
else if ( deadYaw < ent->client->ps.stats[STAT_DEAD_YAW] - 1 )
{
ent->client->ps.stats[STAT_DEAD_YAW]--;
}
else
{
ent->client->ps.stats[STAT_DEAD_YAW] = deadYaw;
}
}
return;
}
// perform once-a-second actions
ClientTimerActions( ent, msec );
ClientEndPowerUps( ent );
//DEBUG INFO
/*
if ( client->ps.clientNum < 1 )
{//Only a player
if ( ucmd->buttons & BUTTON_USE )
{
NAV_PrintLocalWpDebugInfo( ent );
}
}
*/
//try some idle anims on ent if getting no input and not moving for some time
G_CheckClientIdle( ent, ucmd );
}
/*
==================
ClientThink
A new command has arrived from the client
==================
*/
extern void PM_CheckForceUseButton( gentity_t *ent, usercmd_t *ucmd );
extern qboolean PM_GentCantJump( gentity_t *gent );
void ClientThink( int clientNum, usercmd_t *ucmd ) {
gentity_t *ent;
qboolean restore_ucmd = qfalse;
usercmd_t sav_ucmd = {0};
ent = g_entities + clientNum;
if ( !ent->s.number )
{
if ( ent->client->ps.viewEntity > 0 && ent->client->ps.viewEntity < ENTITYNUM_WORLD )
{//you're controlling another NPC
gentity_t *controlled = &g_entities[ent->client->ps.viewEntity];
qboolean freed = qfalse;
if ( controlled->NPC
&& controlled->NPC->controlledTime
&& ent->client->ps.forcePowerLevel[FP_TELEPATHY] > FORCE_LEVEL_3 )
{//An NPC I'm controlling with mind trick
if ( controlled->NPC->controlledTime < level.time )
{//time's up!
G_ClearViewEntity( ent );
freed = qtrue;
}
else if ( ucmd->upmove > 0 )
{//jumping gets you out of it FIXME: check some other button instead... like ESCAPE... so you could even have total control over an NPC?
G_ClearViewEntity( ent );
ucmd->upmove = 0;//ucmd->buttons = 0;
//stop player from doing anything for a half second after
ent->aimDebounceTime = level.time + 500;
freed = qtrue;
}
}
else if ( controlled->NPC //an NPC
&& PM_GentCantJump( controlled ) //that cannot jump
&& controlled->NPC->stats.moveType != MT_FLYSWIM ) //and does not use upmove to fly
{//these types use jump to get out
if ( ucmd->upmove > 0 )
{//jumping gets you out of it FIXME: check some other button instead... like ESCAPE... so you could even have total control over an NPC?
G_ClearViewEntity( ent );
ucmd->upmove = 0;//ucmd->buttons = 0;
//stop player from doing anything for a half second after
ent->aimDebounceTime = level.time + 500;
freed = qtrue;
}
}
else
{//others use the blocking key, button3
if ( (ucmd->buttons&BUTTON_BLOCKING) )
{//jumping gets you out of it FIXME: check some other button instead... like ESCAPE... so you could even have total control over an NPC?
G_ClearViewEntity( ent );
ucmd->buttons = 0;
freed = qtrue;
}
}
if ( !freed )
{//still controlling, save off my ucmd and clear it for my actual run through pmove
restore_ucmd = qtrue;
memcpy( &sav_ucmd, ucmd, sizeof( usercmd_t ) );
memset( ucmd, 0, sizeof( usercmd_t ) );
//to keep pointing in same dir, need to set ucmd->angles
ucmd->angles[PITCH] = ANGLE2SHORT( ent->client->ps.viewangles[PITCH] ) - ent->client->ps.delta_angles[PITCH];
ucmd->angles[YAW] = ANGLE2SHORT( ent->client->ps.viewangles[YAW] ) - ent->client->ps.delta_angles[YAW];
ucmd->angles[ROLL] = 0;
}
else
{
ucmd->angles[PITCH] = ANGLE2SHORT( ent->client->ps.viewangles[PITCH] ) - ent->client->ps.delta_angles[PITCH];
ucmd->angles[YAW] = ANGLE2SHORT( ent->client->ps.viewangles[YAW] ) - ent->client->ps.delta_angles[YAW];
ucmd->angles[ROLL] = 0;
}
}
else if ( ent->client->NPC_class == CLASS_ATST )
{
if ( ucmd->upmove > 0 )
{//get out of ATST
GEntity_UseFunc( ent->activator, ent, ent );
ucmd->upmove = 0;//ucmd->buttons = 0;
}
}
if ( (ucmd->buttons&BUTTON_BLOCKING) && !g_saberAutoBlocking->integer )
{
ucmd->buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK);
}
PM_CheckForceUseButton( ent, ucmd );
}
ent->client->usercmd = *ucmd;
// if ( !g_syncronousClients->integer )
{
ClientThink_real( ent, ucmd );
}
// ClientThink_real can end up freeing this ent, need to check
if ( restore_ucmd && ent->client )
{//restore ucmd for later so NPC you're controlling can refer to them
memcpy( &ent->client->usercmd, &sav_ucmd, sizeof( usercmd_t ) );
}
if ( ent->s.number )
{//NPCs drown, burn from lava, etc, also
P_WorldEffects( ent );
}
}
void ClientEndPowerUps( gentity_t *ent )
{
int i;
if ( ent == NULL || ent->client == NULL )
{
return;
}
// turn off any expired powerups
for ( i = 0 ; i < MAX_POWERUPS ; i++ )
{
if ( ent->client->ps.powerups[ i ] < level.time )
{
ent->client->ps.powerups[ i ] = 0;
}
}
}
/*
==============
ClientEndFrame
Called at the end of each server frame for each connected client
A fast client will have multiple ClientThink for each ClientEdFrame,
while a slow client may have multiple ClientEndFrame between ClientThink.
==============
*/
void ClientEndFrame( gentity_t *ent )
{
//
// If the end of unit layout is displayed, don't give
// the player any normal movement attributes
//
// burn from lava, etc
P_WorldEffects (ent);
// apply all the damage taken this frame
P_DamageFeedback (ent);
// add the EF_CONNECTION flag if we haven't gotten commands recently
if ( level.time - ent->client->lastCmdTime > 1000 ) {
ent->s.eFlags |= EF_CONNECTION;
} else {
ent->s.eFlags &= ~EF_CONNECTION;
}
ent->client->ps.stats[STAT_HEALTH] = ent->health; // FIXME: get rid of ent->health...
// G_SetClientSound (ent);
}