1261 lines
32 KiB
C++
1261 lines
32 KiB
C++
|
|
#include "g_local.h"
|
|
#include "g_functions.h"
|
|
#include "..\cgame\cg_local.h"
|
|
#include "Q3_Interface.h"
|
|
|
|
#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 bool in_camera;
|
|
extern qboolean player_locked;
|
|
extern qboolean stop_icarus;
|
|
extern cvar_t *g_spskill;
|
|
|
|
|
|
/*
|
|
===============
|
|
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;
|
|
}
|
|
|
|
// play an apropriate pain sound
|
|
if ( (level.time > player->painDebounceTime) && !(player->flags & FL_GODMODE) )
|
|
{
|
|
player->painDebounceTime = level.time + 700;
|
|
client->ps.damageEvent++;
|
|
if ( !Q3_TaskIDPending( player, TID_CHAN_VOICE ) )
|
|
{
|
|
G_AddEvent( player, EV_PAIN, player->health );
|
|
}
|
|
}
|
|
|
|
|
|
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 waterlevel;
|
|
|
|
if ( ent->client->noclip ) {
|
|
ent->client->airOutTime = level.time + 12000; // don't need air
|
|
return;
|
|
}
|
|
|
|
waterlevel = ent->waterlevel;
|
|
//
|
|
// check for drowning
|
|
//
|
|
if ( waterlevel == 3 && !(ent->watertype&CONTENTS_LADDER) ) {
|
|
|
|
// if out of air, start drowning
|
|
if ( ent->client->airOutTime < level.time) {
|
|
// 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_Sound(ent, G_SoundIndex("sound/player/hm_male/drown.wav"));
|
|
} else if (rand()&1) {
|
|
G_Sound(ent, G_SoundIndex("sound/player/gurp1.wav"));
|
|
} else {
|
|
G_Sound(ent, G_SoundIndex("sound/player/gurp2.wav"));
|
|
}
|
|
|
|
// 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 (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*waterlevel, 0, MOD_LAVA);
|
|
}
|
|
|
|
if (ent->watertype & CONTENTS_SLIME) {
|
|
G_Damage (ent, NULL, NULL, NULL, NULL,
|
|
1, 0, MOD_SLIME);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
===============
|
|
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;
|
|
}
|
|
|
|
|
|
|
|
//==============================================================
|
|
|
|
/*
|
|
==============
|
|
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 clients don't activate triggers!
|
|
if ( ent->client->ps.stats[STAT_HEALTH] <= 0 ) {
|
|
return;
|
|
}
|
|
|
|
VectorSubtract( ent->currentOrigin, ent->lastOrigin, diff );
|
|
dist = VectorNormalize( diff );
|
|
|
|
memset (touched, qfalse, sizeof(touched) );
|
|
|
|
for ( curDist = 0; !done; 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
|
|
}
|
|
// 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);
|
|
}
|
|
|
|
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_MoverTouchTeleportTriggers( 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_TELEPORT_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_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 Munro NPC
|
|
int newWeap;
|
|
if ( g_entities[0].client->ps.weapon == WP_PHASER || g_entities[0].client->ps.weapon > WP_DREADNOUGHT )//WP_VOYAGER_HYPO
|
|
{
|
|
newWeap = WP_COMPRESSION_RIFLE;
|
|
}
|
|
else
|
|
{
|
|
newWeap = g_entities[0].client->ps.weapon;
|
|
}
|
|
if ( newWeap != WP_NONE && ent->client->ps.weapon != newWeap )
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
=================
|
|
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\"" );
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
|
|
/*
|
|
====================
|
|
ClientIntermissionThink
|
|
====================
|
|
*/
|
|
static qboolean ClientCinematicThink( gclient_t *client ) {
|
|
client->ps.eFlags &= ~EF_TALK;
|
|
client->ps.eFlags &= ~EF_FIRING;
|
|
|
|
// swap button actions
|
|
client->oldbuttons = client->buttons;
|
|
client->buttons = client->usercmd.buttons;
|
|
if ( client->buttons & ( /*BUTTON_ATTACK |*/ BUTTON_USE_HOLDABLE ) & ( 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
|
|
================
|
|
*/
|
|
void ClientEvents( gentity_t *ent, int oldEventSequence ) {
|
|
int i;
|
|
int event;
|
|
gclient_t *client;
|
|
int damage;
|
|
qboolean fired;
|
|
|
|
client = ent->client;
|
|
|
|
fired = qfalse;
|
|
|
|
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" );
|
|
}
|
|
#endif
|
|
fired = qtrue;
|
|
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" );
|
|
}
|
|
#endif
|
|
fired = qtrue;
|
|
FireWeapon( ent, qtrue );
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void BG_AddPushVecToUcmd(gentity_t *self, usercmd_t *ucmd)
|
|
{
|
|
vec3_t forward, right, moveDir;
|
|
float pushSpeed, fMove, rMove;
|
|
|
|
pushSpeed = VectorLengthSquared(self->s.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->s.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
|
|
|
|
VectorClear(self->s.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_BORG: //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;
|
|
|
|
switch ( ent->client->playerTeam )
|
|
{
|
|
case TEAM_BORG:
|
|
runSpeed = ent->NPC->stats.runSpeed;
|
|
|
|
runSpeed += BORG_RUN_INCR * (g_spskill->integer%3);
|
|
break;
|
|
|
|
case TEAM_8472:
|
|
runSpeed = ent->NPC->stats.runSpeed;
|
|
runSpeed += SPECIES_RUN_INCR * (g_spskill->integer%3);
|
|
break;
|
|
|
|
case TEAM_STASIS:
|
|
runSpeed = ent->NPC->stats.runSpeed;
|
|
runSpeed += STASIS_RUN_INCR * (g_spskill->integer%3);
|
|
break;
|
|
|
|
case TEAM_BOTS:
|
|
|
|
//Only for warbot
|
|
if ( ( Q_stricmp( ent->NPC_type, "warriorbot" ) == 0 ) || ( Q_stricmp( ent->NPC_type, "warriorbot_boss" ) == 0 ) )
|
|
{
|
|
runSpeed = ent->NPC->stats.runSpeed;
|
|
runSpeed += WARBOT_RUN_INCR * (g_spskill->integer%3);
|
|
break;
|
|
}
|
|
|
|
//NOTENOTE: Falls through for other bots
|
|
|
|
default:
|
|
runSpeed = ent->NPC->stats.runSpeed;
|
|
break;
|
|
}
|
|
|
|
return runSpeed;
|
|
}
|
|
|
|
/*
|
|
==============
|
|
ClientThink
|
|
|
|
This will be called once for each client frame, which will
|
|
usually be a couple times for each server frame on fast clients.
|
|
|
|
==============
|
|
*/
|
|
|
|
void ClientThink_real( gentity_t *ent, usercmd_t *ucmd )
|
|
{
|
|
gclient_t *client;
|
|
pmove_t pm;
|
|
vec3_t oldOrigin;
|
|
int oldEventSequence;
|
|
int msec;
|
|
|
|
//Don't let the player do anything if in a camera
|
|
if ( ent->s.number == 0 ) {
|
|
extern cvar_t *g_skippingcin;
|
|
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...
|
|
gi.cvar_set("skippingCinematic", "0");
|
|
gi.cvar_set("timescale", "1");
|
|
}
|
|
else
|
|
{
|
|
// no... so start skipping...
|
|
gi.cvar_set("skippingCinematic", "1");
|
|
gi.cvar_set("timescale", "100");
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
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
|
|
if (Q_stricmpn(level.mapname,"_holo",5)) {
|
|
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 ( player_locked && ent->client->ps.pm_type < PM_DEAD ) {//lock out player control unless dead
|
|
VectorClear(ucmd->angles) ;
|
|
ucmd->forwardmove = 0;
|
|
ucmd->rightmove = 0;
|
|
ucmd->buttons = 0;
|
|
ucmd->upmove = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
G_NPCMunroMatchPlayerWeapon( ent );
|
|
}
|
|
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 = (ucmd->upmove && ent->NPC->stats.moveType == MT_FLYSWIM);
|
|
qboolean Climbing = (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 )
|
|
{//At higher speeds, need to slow down close to stuff
|
|
//Slow down as you approach your goal
|
|
if ( ent->NPC->distToGoal < SLOWDOWN_DIST && client->race != RACE_BORG && !(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, (ent->NPC->behaviorState==BS_FORMATION), (ent->NPC->behaviorState==BS_FORMATION) );
|
|
|
|
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;
|
|
//Slow down on turns - don't orbit!!!
|
|
float 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
|
|
client->ps.speed = g_speed->value;//default is 320
|
|
}
|
|
|
|
//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
|
|
}
|
|
}
|
|
}
|
|
|
|
//FIXME: need to do this before check to avoid walls and cliffs (or just cliffs?)
|
|
BG_AddPushVecToUcmd( ent, ucmd );
|
|
|
|
BG_CalculateOffsetAngles( ent, ucmd );
|
|
|
|
// 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 = 0;//( 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
|
|
|
|
VectorCopy (pm.mins, ent->mins);
|
|
VectorCopy (pm.maxs, ent->maxs);
|
|
|
|
ent->waterlevel = pm.waterlevel;
|
|
ent->watertype = pm.watertype;
|
|
|
|
VectorCopy( 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 | BUTTON_USE_HOLDABLE ) )
|
|
{
|
|
respawn( ent );
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if ((cg.missionStatusShow) && ((cg.missionStatusDeadTime + 1) < level.time))
|
|
{
|
|
if ( ucmd->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) )
|
|
{
|
|
cg.missionStatusShow = 0;
|
|
ScoreBoardReset();
|
|
// Q3_TaskIDComplete( ent, TID_MISSIONSTATUS );
|
|
}
|
|
}
|
|
// perform once-a-second actions
|
|
//ClientTimerActions( ent, msec );
|
|
|
|
//DEBUG INFO
|
|
/*
|
|
if ( client->ps.clientNum < 1 )
|
|
{//Only a player
|
|
if ( ucmd->buttons & BUTTON_USE )
|
|
{
|
|
NAV_PrintLocalWpDebugInfo( ent );
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
|
|
/*
|
|
==================
|
|
ClientThink
|
|
|
|
A new command has arrived from the client
|
|
==================
|
|
*/
|
|
void ClientThink( int clientNum, usercmd_t *ucmd ) {
|
|
gentity_t *ent;
|
|
|
|
ent = g_entities + clientNum;
|
|
ent->client->usercmd = *ucmd;
|
|
// if ( !g_syncronousClients->integer )
|
|
{
|
|
ClientThink_real( ent, ucmd );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
==============
|
|
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 ) {
|
|
int i;
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
//
|
|
// 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);
|
|
}
|
|
|
|
|