mirror of
https://github.com/UberGames/rpgxEF.git
synced 2024-11-15 09:21:27 +00:00
a39565b783
... not quite content with where the project files lie but it is ok for now. ... compiling works fine so far (only tested mingw32 right now)
2470 lines
66 KiB
C
2470 lines
66 KiB
C
// Copyright (C) 1999-2000 Id Software, Inc.
|
|
//
|
|
|
|
#include "g_local.h"
|
|
|
|
extern void SP_misc_ammo_station( gentity_t *ent );
|
|
extern void ammo_station_finish_spawning ( gentity_t *self );
|
|
extern int borgQueenClientNum;
|
|
extern qboolean BG_BorgTransporting( playerState_t *ps );
|
|
|
|
static int lastTimedMessage; //TiM - Moved here from g_local.h
|
|
|
|
/*
|
|
==============
|
|
TryUse
|
|
|
|
Try and use an entity in the world, directly ahead of us
|
|
==============
|
|
*/
|
|
|
|
#define USE_DISTANCE 64.0f
|
|
void TryUse( gentity_t *ent )
|
|
{
|
|
gentity_t *target; //, *newent;
|
|
trace_t trace;
|
|
vec3_t src, dest, vf;
|
|
int i;
|
|
|
|
VectorCopy( ent->r.currentOrigin, src );
|
|
src[2] += ent->client->ps.viewheight;
|
|
|
|
AngleVectors( ent->client->ps.viewangles, vf, NULL, NULL );
|
|
|
|
//extend to find end of use trace
|
|
VectorMA( src, -6, vf, src );//in case we're inside something?
|
|
VectorMA( src, 134, vf, dest );//128+6
|
|
|
|
//Trace ahead to find a valid target
|
|
trap_Trace( &trace, src, vec3_origin, vec3_origin, dest, ent->s.number, MASK_OPAQUE|CONTENTS_BODY|CONTENTS_ITEM|CONTENTS_CORPSE );
|
|
|
|
if ( trace.fraction == 1.0f || trace.entityNum < 0 )
|
|
{
|
|
//FIXME: Play a failure sound
|
|
return;
|
|
}
|
|
|
|
target = &g_entities[trace.entityNum];
|
|
|
|
//Check for a use command
|
|
if ( target->client )
|
|
{//if a bot, make it follow me, if a teammate, give stuff
|
|
if ( target->client->sess.sessionTeam == ent->client->sess.sessionTeam )
|
|
{//on my team
|
|
// gitem_t *regen = BG_FindItemForPowerup( PW_REGEN );
|
|
// gitem_t *invis = BG_FindItemForPowerup( PW_INVIS );
|
|
|
|
switch( ent->client->sess.sessionClass )
|
|
{
|
|
case PC_MEDIC://medic gives regen
|
|
break;
|
|
case PC_TECH://tech gives invisibility
|
|
break;
|
|
}
|
|
switch( target->client->sess.sessionClass )
|
|
{
|
|
case PC_TECH://using tech gives you full ammo
|
|
G_Sound(ent, G_SoundIndex("sound/player/suitenergy.wav") );
|
|
for ( i = 0 ; i < MAX_WEAPONS ; i++ )
|
|
{
|
|
ent->client->ps.ammo[i] = Max_Ammo[i];
|
|
}
|
|
return;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if ( target->use && Q_stricmp("func_usable", target->classname) == 0 )
|
|
{//usable brush
|
|
if ( target->team && atoi( target->team ) != 0 )
|
|
{//usable has a team
|
|
if ( atoi( target->team ) != ent->client->sess.sessionTeam )
|
|
{//not on my team
|
|
if ( ent->client->sess.sessionClass != PC_TECH )
|
|
{//only a tech can use enemy usables
|
|
//FIXME: Play a failure sound
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
//FIXME: play sound?
|
|
target->use( target, ent, ent );
|
|
return;
|
|
}
|
|
else if ( target->use && Q_stricmp("misc_ammo_station", target->classname) == 0 )
|
|
{//ammo station
|
|
if ( ent->client->sess.sessionTeam )
|
|
{
|
|
if ( target->team )
|
|
{
|
|
if ( atoi( target->team ) != ent->client->sess.sessionTeam )
|
|
{
|
|
//FIXME: play sound?
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
target->use( target, ent, ent );
|
|
return;
|
|
}
|
|
else if ( target->s.number == ENTITYNUM_WORLD || (target->s.pos.trType == TR_STATIONARY && !(trace.surfaceFlags & SURF_NOIMPACT) && !target->takedamage) )
|
|
{
|
|
switch( ent->client->sess.sessionClass )
|
|
{
|
|
case PC_TECH://tech can place an ammo station
|
|
break;
|
|
}
|
|
//FIXME: play sound
|
|
return;
|
|
}
|
|
//FIXME: Play a failure sound
|
|
}
|
|
|
|
/*
|
|
===============
|
|
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->pain_debounce_time) && !(player->flags & FL_GODMODE) ) {
|
|
player->pain_debounce_time = level.time + 700;
|
|
G_AddEvent( player, EV_PAIN, player->health );
|
|
client->ps.damageEvent++;
|
|
}
|
|
|
|
client->ps.damageCount = client->damage_blood;
|
|
if (client->ps.damageCount > 255)
|
|
{
|
|
client->ps.damageCount = 255;
|
|
}
|
|
|
|
client->ps.damageShieldCount = client->damage_armor;
|
|
if (client->ps.damageShieldCount > 255)
|
|
{
|
|
client->ps.damageShieldCount = 255;
|
|
}
|
|
|
|
//
|
|
// 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 ) {
|
|
//qboolean envirosuit;
|
|
int waterlevel;
|
|
|
|
if ( ent->client->noclip ) {
|
|
ent->client->airOutTime = level.time + 12000; // don't need air
|
|
return;
|
|
}
|
|
|
|
waterlevel = ent->waterlevel;
|
|
|
|
//envirosuit = ent->client->ps.powerups[PW_BOLTON] > level.time;
|
|
|
|
//
|
|
// check for drowning
|
|
//
|
|
if ( waterlevel == 3 && !(ent->watertype&CONTENTS_LADDER)) {
|
|
// envirosuit give air, techs can't drown
|
|
if ( /*envirosuit ||*/ ent->client->sess.sessionClass == PC_ALPHAOMEGA22 ) {
|
|
ent->client->airOutTime = level.time + 10000;
|
|
}
|
|
|
|
// if out of air, start drowning
|
|
if ( ent->client->airOutTime < level.time) {
|
|
// drown!
|
|
ent->client->airOutTime += 1000;
|
|
if ( ent->health > 1 ) { //TiM : used to be 0, but to fix red's medic code
|
|
// 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("*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->pain_debounce_time = 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->pain_debounce_time < level.time ) {
|
|
|
|
/* if ( envirosuit ) {
|
|
G_AddEvent( ent, EV_POWERUP_BATTLESUIT, 0 );
|
|
} else {*/
|
|
if (ent->watertype & CONTENTS_LAVA) {
|
|
G_Damage (ent, NULL, NULL, NULL, NULL,
|
|
30*waterlevel, 0, MOD_LAVA);
|
|
}
|
|
|
|
if (ent->watertype & CONTENTS_SLIME) {
|
|
G_Damage (ent, NULL, NULL, NULL, NULL,
|
|
10*waterlevel, 0, MOD_SLIME);
|
|
}
|
|
//}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
===============
|
|
G_SetClientSound
|
|
===============
|
|
*/
|
|
void G_SetClientSound( gentity_t *ent )
|
|
{ // 3/28/00 kef -- this is dumb.
|
|
if (ent->waterlevel && (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) )
|
|
ent->s.loopSound = level.snd_fry;
|
|
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->r.svFlags & SVF_BOT ) && ( ent->touch ) ) {
|
|
ent->touch( ent, other, &trace );
|
|
}
|
|
|
|
if ( !other->touch ) {
|
|
continue;
|
|
}
|
|
|
|
other->touch( other, ent, &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;
|
|
int touch[MAX_GENTITIES];
|
|
gentity_t *hit;
|
|
trace_t trace;
|
|
vec3_t mins, maxs;
|
|
static 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 = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
|
|
|
|
// can't use ent->absmin, because that has a one unit pad
|
|
VectorAdd( ent->client->ps.origin, ent->r.mins, mins );
|
|
VectorAdd( ent->client->ps.origin, ent->r.maxs, maxs );
|
|
|
|
for ( i=0 ; i<num ; i++ ) {
|
|
hit = &g_entities[touch[i]];
|
|
|
|
if ( !hit->touch && !ent->touch ) {
|
|
continue;
|
|
}
|
|
if ( !( hit->r.contents & CONTENTS_TRIGGER ) ) {
|
|
continue;
|
|
}
|
|
|
|
// ignore most entities if a spectator
|
|
if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR || (ent->client->ps.eFlags&EF_ELIMINATED) )
|
|
{
|
|
//RPG-X: J2J - No clip spectating doesnt need to use transporter entities or door triggers!
|
|
if( rpg_noclipspectating.integer == 1 )
|
|
continue;
|
|
|
|
// this is ugly but adding a new ET_? type will
|
|
// most likely cause network incompatibilities
|
|
if ( hit->s.eType != ET_TELEPORT_TRIGGER && hit->touch != Touch_DoorTrigger)
|
|
{
|
|
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 ( !trap_EntityContact( mins, maxs, hit ) ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
memset( &trace, 0, sizeof(trace) );
|
|
|
|
if ( hit->touch ) {
|
|
hit->touch (hit, ent, &trace);
|
|
}
|
|
|
|
if ( ( ent->r.svFlags & SVF_BOT ) && ( ent->touch ) ) {
|
|
ent->touch( ent, hit, &trace );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
SpectatorThink
|
|
=================
|
|
*/
|
|
void SpectatorThink( gentity_t *ent, usercmd_t *ucmd ) {
|
|
pmove_t pm;
|
|
gclient_t *client;
|
|
|
|
client = ent->client;
|
|
|
|
if ( client->sess.spectatorState != SPECTATOR_FOLLOW ) {
|
|
client->ps.pm_type = PM_SPECTATOR;
|
|
client->ps.speed = 400; // faster than normal
|
|
|
|
// set up for pmove
|
|
memset (&pm, 0, sizeof(pm));
|
|
pm.ps = &client->ps;
|
|
pm.cmd = *ucmd;
|
|
pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; // spectators can fly through bodies
|
|
pm.trace = trap_Trace;
|
|
pm.pointcontents = trap_PointContents;
|
|
|
|
// perform a pmove
|
|
Pmove (&pm);
|
|
|
|
// save results of pmove
|
|
VectorCopy( client->ps.origin, ent->s.origin );
|
|
|
|
G_TouchTriggers( ent );
|
|
trap_UnlinkEntity( ent );
|
|
}
|
|
|
|
client->oldbuttons = client->buttons;
|
|
client->buttons = ucmd->buttons;
|
|
|
|
// attack button cycles through spectators
|
|
if ( ( client->buttons & BUTTON_ATTACK ) && ! ( client->oldbuttons & BUTTON_ATTACK ) ) {
|
|
Cmd_FollowCycle_f( ent, 1 );
|
|
}
|
|
else if ( ( client->buttons & BUTTON_ALT_ATTACK ) && ! ( client->oldbuttons & BUTTON_ALT_ATTACK ) )
|
|
{
|
|
if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) {
|
|
StopFollowing( 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->pers.cmd.forwardmove ||
|
|
client->pers.cmd.rightmove ||
|
|
client->pers.cmd.upmove ||
|
|
(client->pers.cmd.buttons & BUTTON_ATTACK) ||
|
|
(client->pers.cmd.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 )
|
|
{
|
|
trap_DropClient( client - level.clients, "Dropped due to inactivity" );
|
|
return qfalse;
|
|
}
|
|
if ( level.time > client->inactivityTime - 10000 && !client->inactivityWarning )
|
|
{
|
|
client->inactivityWarning = qtrue;
|
|
trap_SendServerCommand( client - level.clients, "cp \"Ten seconds until inactivity drop!\n\"" );
|
|
}
|
|
}
|
|
return qtrue;
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
TimedMessage
|
|
|
|
RPG-X - RedTechie: Returns the message requested.
|
|
If the message is blank go to next. (Based off of SFEFMOD)
|
|
|
|
TiM: Huh... O_o. Damn Red, you're right. If the admin
|
|
puts in values, but not in a consistent consecutive order,
|
|
we'll get some mighty painful errors. >.<
|
|
|
|
I guess what we really need is a for loop that goes
|
|
thru, and checks to see if each and every CVAR has a value
|
|
currently in it....
|
|
==================
|
|
*/
|
|
|
|
//First off let's store the CVAR data in a local array.
|
|
//This'll let us perform for loop operations on it.
|
|
//Sigh, I wish I knew a quick way to convert variable names
|
|
//to strings. Then this wouldn't be necessary O_o
|
|
static char *rpg_message[] = { rpg_message1.string,
|
|
rpg_message2.string,
|
|
rpg_message3.string,
|
|
rpg_message4.string,
|
|
rpg_message5.string,
|
|
rpg_message6.string,
|
|
rpg_message7.string,
|
|
rpg_message8.string,
|
|
rpg_message9.string,
|
|
rpg_message10.string };
|
|
|
|
char *TimedMessage( void ){
|
|
int i = lastTimedMessage;
|
|
|
|
while ( 1 ) { //Okay, start from the number we want, and loop thru all of them
|
|
if ( i == 11 ) { //reset loop
|
|
i = 1;
|
|
}
|
|
|
|
if ( strlen( rpg_message[i-1] ) >= 1 ) { //i-1 coz arrays start @ 0, but we started @ 1
|
|
lastTimedMessage = i;
|
|
|
|
return rpg_message[i-1]; //return the string
|
|
}
|
|
|
|
if ( i == lastTimedMessage - 1 ) { //okay, we've obviously gone thru the whole loop and come up with nothing. So screw it.
|
|
return NULL;
|
|
}
|
|
|
|
//TiM: Cheap hack to stop it freezing if string0 is empty. THe above condition don't work here
|
|
if ( ( i == lastTimedMessage == 1 ) && !strlen( rpg_message[i-1] ) ) {
|
|
return NULL;
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
//TiM: Hopefully it'll never reach here, but we have this anyway to shut the compiler up
|
|
return "^1RPG-X ERROR: No messages to display";
|
|
|
|
|
|
/*
|
|
switch( Msg ){
|
|
case 1:
|
|
if(strlen(rpg_message1.string) >= 1){
|
|
lastTimedMessage = 1;
|
|
return rpg_message1.string;
|
|
}else{
|
|
return TimedMessage( 2 );
|
|
}
|
|
break;
|
|
|
|
case 2:
|
|
if(strlen(rpg_message2.string) >= 1){
|
|
lastTimedMessage = 2;
|
|
return rpg_message2.string;
|
|
}else{
|
|
return TimedMessage( 3 );
|
|
}
|
|
break;
|
|
|
|
case 3:
|
|
if(strlen(rpg_message3.string) >= 1){
|
|
lastTimedMessage = 3;
|
|
return rpg_message3.string;
|
|
}else{
|
|
return TimedMessage( 4 );
|
|
}
|
|
break;
|
|
|
|
case 4:
|
|
if(strlen(rpg_message4.string) >= 1){
|
|
lastTimedMessage = 4;
|
|
return rpg_message4.string;
|
|
}else{
|
|
return TimedMessage( 5 );
|
|
}
|
|
break;
|
|
|
|
case 5:
|
|
if(strlen(rpg_message5.string) >= 1){
|
|
lastTimedMessage = 5;
|
|
return rpg_message5.string;
|
|
}else{
|
|
return TimedMessage( 6 );
|
|
}
|
|
break;
|
|
|
|
case 6:
|
|
if(strlen(rpg_message6.string) >= 1){
|
|
lastTimedMessage = 6;
|
|
return rpg_message6.string;
|
|
}else{
|
|
return TimedMessage( 7 );
|
|
}
|
|
break;
|
|
|
|
case 7:
|
|
if(strlen(rpg_message7.string) >= 1){
|
|
lastTimedMessage = 7;
|
|
return rpg_message7.string;
|
|
}else{
|
|
return TimedMessage( 8 );
|
|
}
|
|
break;
|
|
|
|
case 8:
|
|
if(strlen(rpg_message8.string) >= 1){
|
|
lastTimedMessage = 8;
|
|
return rpg_message8.string;
|
|
}else{
|
|
return TimedMessage( 9 );
|
|
}
|
|
break;
|
|
|
|
case 9:
|
|
if(strlen(rpg_message9.string) >= 1){
|
|
lastTimedMessage = 9;
|
|
return rpg_message9.string;
|
|
}else{
|
|
return TimedMessage( 10 );
|
|
}
|
|
break;
|
|
|
|
case 10:
|
|
if(strlen(rpg_message10.string) >= 1){
|
|
lastTimedMessage = 0; //Start over again
|
|
return rpg_message10.string;
|
|
}else{
|
|
return TimedMessage( 1 );
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return "^1RPG-X ERROR: No messages to display";
|
|
break;
|
|
}*/
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
ClientTimerActions
|
|
|
|
Actions that happen once a second
|
|
==================
|
|
*/
|
|
|
|
void ClientTimerActions( gentity_t *ent, int msec ) {
|
|
gclient_t *client;
|
|
char *message;
|
|
float messageTime;
|
|
|
|
client = ent->client;
|
|
client->timeResidual += msec;
|
|
|
|
//RPG-X - RedTechie: Heavily Modifyed Message system (Based off of phenix's old code)
|
|
/*if ( level.time >= (level.message + (rpg_timedmessagetime.integer * 60000)) ) {
|
|
level.message = level.time;
|
|
trap_SendServerCommand( -1, va("cp \"%s\n\"", rpg_timedmessage.string) );
|
|
//trap_SendServerCommand( -1, va( "print \"%s\n\"", rpg_timedmessage ) );
|
|
}*/
|
|
if( rpg_timedmessagetime.value ){
|
|
//Make sure its not less then one //TiM: Well... we can have under 1, just not toooo far under 1
|
|
|
|
//TiM : Init/reset TimedMessage's value
|
|
if ( lastTimedMessage <= 0 || lastTimedMessage > 10 ) {
|
|
lastTimedMessage = 1;
|
|
}
|
|
|
|
if(rpg_timedmessagetime.value < 0.2) { //1
|
|
messageTime = 0.2;
|
|
}else{
|
|
messageTime = rpg_timedmessagetime.value;
|
|
}
|
|
|
|
if (level.time > (level.message + (messageTime * 60000)) ) {
|
|
level.message = level.time;
|
|
|
|
//TiM - There. So with this working in conjunction with that reset
|
|
//code above, this should be more efficient. :)
|
|
message = TimedMessage(); //Since we're working with a gloabl scope variable, there's no need for this thing to have parameters:)
|
|
lastTimedMessage++;
|
|
|
|
/*//Decide what timed message to display
|
|
if( lastTimedMessage && lastTimedMessage != 0 ){
|
|
//A message has been displayed
|
|
if(lastTimedMessage == 1){
|
|
//Display message 2
|
|
message = TimedMessage( 2 );
|
|
}else if(lastTimedMessage == 2){
|
|
//Display message 3
|
|
message = TimedMessage( 3 );
|
|
}else if(lastTimedMessage == 3){
|
|
//Display message 4
|
|
message = TimedMessage( 4 );
|
|
}else if(lastTimedMessage == 4){
|
|
//Display message 5
|
|
message = TimedMessage( 5 );
|
|
}else if(lastTimedMessage == 5){
|
|
//Display message 6
|
|
message = TimedMessage( 6 );
|
|
}else if(lastTimedMessage == 6){
|
|
//Display message 7
|
|
message = TimedMessage( 7 );
|
|
}else if(lastTimedMessage == 7){
|
|
//Display message 8
|
|
message = TimedMessage( 8 );
|
|
}else if(lastTimedMessage == 8){
|
|
//Display message 9
|
|
message = TimedMessage( 9 );
|
|
}else if(lastTimedMessage == 9){
|
|
//Display message 10
|
|
message = TimedMessage( 10 );
|
|
}else{
|
|
//BAD
|
|
message = "^1RPG-X ERROR: lastTimedMessage not set correctly.";
|
|
}
|
|
}else{
|
|
//A message hasnt been displayed yet
|
|
message = TimedMessage( 1 );
|
|
}*/
|
|
|
|
//Alright send the message now
|
|
if ( message != NULL ) {
|
|
trap_SendServerCommand( -1, va("cp \"%s\n\"", message) ); //Shows the message on their main screen
|
|
}
|
|
|
|
//trap_SendServerCommand( -1, va("print \"\n \n%s \n\"", message)); //Shows the message in console
|
|
}
|
|
}
|
|
|
|
|
|
|
|
while ( client->timeResidual >= 1000 ) {
|
|
client->timeResidual -= 1000;
|
|
|
|
// regenerate
|
|
//TiM - removed so we can use the REGEN powerup elsewhere
|
|
/*if ( client->ps.powerups[PW_REGEN] )
|
|
{
|
|
if ( client->sess.sessionClass != PC_NOCLASS && client->sess.sessionClass != PC_BORG && client->sess.sessionClass != PC_ACTIONHERO )
|
|
{
|
|
if ( ent->health < client->ps.stats[STAT_MAX_HEALTH] )
|
|
{
|
|
ent->health += 2;
|
|
G_AddEvent( ent, EV_POWERUP_REGEN, 0 );
|
|
}
|
|
}
|
|
else if ( ent->health < client->ps.stats[STAT_MAX_HEALTH])
|
|
{
|
|
ent->health += 15;
|
|
if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] * 1.1 )
|
|
{
|
|
ent->health = client->ps.stats[STAT_MAX_HEALTH] * 1.1;
|
|
}
|
|
G_AddEvent( ent, EV_POWERUP_REGEN, 0 );
|
|
}
|
|
else if ( ent->health < client->ps.stats[STAT_MAX_HEALTH] * 2)
|
|
{
|
|
ent->health += 5;
|
|
if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] * 2 )
|
|
{
|
|
ent->health = client->ps.stats[STAT_MAX_HEALTH] * 2;
|
|
}
|
|
G_AddEvent( ent, EV_POWERUP_REGEN, 0 );
|
|
}
|
|
}*/
|
|
/*else if (ent->flags & FL_CLOAK)
|
|
{
|
|
//RPG-X: RedTechie - Health dosnt matter
|
|
if ( ent->health < 41)
|
|
{
|
|
ent->flags ^= FL_CLOAK;
|
|
ent->client->ps.powerups[PW_INVIS] = level.time + 1000000000;
|
|
}
|
|
}
|
|
else if (ent->flags & FL_FLY)
|
|
{
|
|
//RPG-X: RedTechie - Health dosnt matter
|
|
if ( ent->health < 41)
|
|
{
|
|
ent->flags ^= FL_FLY;
|
|
ent->client->ps.powerups[PW_FLIGHT] = level.time + 1000000000;
|
|
}
|
|
}*/
|
|
//else
|
|
//{
|
|
// count down health when over max and not cloaked.
|
|
if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] )
|
|
{
|
|
ent->health--;
|
|
}
|
|
//}
|
|
|
|
// THIS USED TO count down armor when over max, more slowly now (every other second)
|
|
// if ( client->ps.stats[STAT_ARMOR] > client->ps.stats[STAT_MAX_HEALTH] && ((int)(level.time/1000))&0x01) {
|
|
|
|
// NOW IT ONCE AGAIN counts down armor when over max, once per second
|
|
if ( client->ps.stats[STAT_ARMOR] > client->ps.stats[STAT_MAX_HEALTH]) {
|
|
client->ps.stats[STAT_ARMOR]--;
|
|
}
|
|
|
|
// if we've got the seeker powerup, see if we can shoot it at someone
|
|
//if ( client->ps.powerups[PW_SEEKER] )
|
|
//{
|
|
//vec3_t seekerPos;
|
|
|
|
//TiM - Because I commented out the scav code, I'm going to remove all other ref to it
|
|
/*if (SeekerAcquiresTarget(ent, seekerPos)) // set the client's enemy to a valid target
|
|
{
|
|
FireSeeker( ent, ent->enemy, seekerPos );
|
|
G_AddEvent( ent, EV_POWERUP_SEEKER_FIRE, 0 ); // draw the thingy
|
|
}*/
|
|
//}
|
|
|
|
if ( !client->ps.stats[STAT_HOLDABLE_ITEM] )
|
|
{//holding nothing...
|
|
if ( client->ps.stats[STAT_USEABLE_PLACED] > 0 )
|
|
{//we're in some kind of countdown
|
|
//so count down
|
|
client->ps.stats[STAT_USEABLE_PLACED]--;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
/*
|
|
====================
|
|
ClientIntermissionThink
|
|
====================
|
|
*/
|
|
void ClientIntermissionThink( gclient_t *client ) {
|
|
client->ps.eFlags &= ~EF_TALK;
|
|
client->ps.eFlags &= ~EF_FIRING;
|
|
|
|
// the level will exit when everyone wants to or after timeouts
|
|
|
|
// swap and latch button actions
|
|
client->oldbuttons = client->buttons;
|
|
client->buttons = client->pers.cmd.buttons;
|
|
if (g_gametype.integer != GT_SINGLE_PLAYER)
|
|
{
|
|
if ( client->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) & ( client->oldbuttons ^ client->buttons ) ) {
|
|
client->readyToExit ^= 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
====================
|
|
Cmd_Ready_f
|
|
====================
|
|
|
|
This function is called from the ui_sp_postgame.c as a result of clicking on the
|
|
"next" button in non GT_TOURNAMENT games. This replaces the old system of waiting
|
|
for the user to click an ATTACK or USE button to signal ready
|
|
(see ClientIntermissionThink() above)
|
|
|
|
when all clients have signaled ready, the game continues to the next match.
|
|
*/
|
|
void Cmd_Ready_f (gentity_t *ent)
|
|
{
|
|
gclient_t *client;
|
|
client = ent->client;
|
|
|
|
client->readyToExit ^= 1;
|
|
}
|
|
|
|
|
|
|
|
typedef struct detHit_s
|
|
{
|
|
gentity_t *detpack;
|
|
gentity_t *player;
|
|
int time;
|
|
} detHit_t;
|
|
|
|
#define MAX_DETHITS 32 // never define this to be 0
|
|
|
|
detHit_t detHits[MAX_DETHITS];
|
|
qboolean bDetInit = qfalse;
|
|
|
|
extern qboolean FinishSpawningDetpack( gentity_t *ent, int itemIndex );
|
|
//-----------------------------------------------------------------------------DECOY TEMP
|
|
extern qboolean FinishSpawningDecoy( gentity_t *ent, int itemIndex );
|
|
//-----------------------------------------------------------------------------DECOY TEMP
|
|
void DetonateDetpack(gentity_t *ent);
|
|
|
|
#define DETPACK_DAMAGE 750
|
|
#define DETPACK_RADIUS 500
|
|
|
|
void detpack_shot( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath )
|
|
{
|
|
int i = 0;
|
|
gentity_t *ent = NULL;
|
|
|
|
//so we can't be blown up by things we're blowing up
|
|
self->takedamage = 0;
|
|
|
|
G_TempEntity(self->s.origin, EV_GRENADE_EXPLODE);
|
|
G_RadiusDamage( self->s.origin, self->parent?self->parent:self, DETPACK_DAMAGE*0.125, DETPACK_RADIUS*0.25,
|
|
self, DAMAGE_ALL_TEAMS, MOD_DETPACK );
|
|
// we're blowing up cuz we've been shot, so make sure we remove ourselves
|
|
//from our parent's inventory (so to speak)
|
|
for (i = 0; i < MAX_CLIENTS; i++)
|
|
{
|
|
if (((ent = &g_entities[i])!=NULL) && ent->inuse && (self->parent == ent))
|
|
{
|
|
ent->client->ps.stats[STAT_USEABLE_PLACED] = 0;
|
|
ent->client->ps.stats[STAT_HOLDABLE_ITEM] = 0;
|
|
break;
|
|
}
|
|
}
|
|
G_FreeEntity(self);
|
|
}
|
|
|
|
qboolean PlaceDetpack(gentity_t *ent)
|
|
{
|
|
gentity_t *detpack = NULL;
|
|
static gitem_t *detpackItem = NULL;
|
|
float detDistance = 80;
|
|
trace_t tr;
|
|
vec3_t fwd, right, up, end, mins = {-16,-16,-16}, maxs = {16,16,16};
|
|
|
|
if (detpackItem == NULL)
|
|
{
|
|
detpackItem = BG_FindItemForHoldable(HI_DETPACK);
|
|
}
|
|
|
|
// make sure our detHit info is init'd
|
|
if (!bDetInit)
|
|
{
|
|
memset(detHits, 0, MAX_DETHITS*sizeof(detHit_t));
|
|
bDetInit = 1;
|
|
}
|
|
|
|
// can we place this in front of us?
|
|
AngleVectors (ent->client->ps.viewangles, fwd, right, up);
|
|
fwd[2] = 0;
|
|
VectorMA(ent->client->ps.origin, detDistance, fwd, end);
|
|
trap_Trace (&tr, ent->client->ps.origin, mins, maxs, end, ent->s.number, MASK_SHOT );
|
|
if (tr.fraction > 0.9)
|
|
{
|
|
// got enough room so place the detpack
|
|
detpack = G_Spawn();
|
|
G_SpawnItem(detpack, detpackItem);
|
|
detpack->physicsBounce = 0.0f;//detpacks are *not* bouncy
|
|
VectorMA(ent->client->ps.origin, detDistance + mins[0], fwd, detpack->s.origin);
|
|
if ( !FinishSpawningDetpack(detpack, detpackItem - bg_itemlist) )
|
|
{
|
|
return qfalse;
|
|
}
|
|
VectorNegate(fwd, fwd);
|
|
vectoangles(fwd, detpack->s.angles);
|
|
detpack->think = DetonateDetpack;
|
|
detpack->nextthink = level.time + 120000; // if not used after 2 minutes it blows up anyway
|
|
detpack->parent = ent;
|
|
return qtrue;
|
|
}
|
|
else
|
|
{
|
|
// no room
|
|
return qfalse;
|
|
}
|
|
}
|
|
|
|
qboolean PlayerHitByDet(gentity_t *det, gentity_t *player)
|
|
{
|
|
int i = 0;
|
|
|
|
if (!bDetInit)
|
|
{
|
|
// detHit stuff not initialized. who knows what's going on?
|
|
return qfalse;
|
|
}
|
|
for (i = 0; i < MAX_DETHITS; i++)
|
|
{
|
|
if ( (detHits[i].detpack == det) && (detHits[i].player == player) )
|
|
{
|
|
return qtrue;
|
|
}
|
|
}
|
|
return qfalse;
|
|
}
|
|
|
|
void AddPlayerToDetHits(gentity_t *det, gentity_t *player)
|
|
{
|
|
int i = 0;
|
|
detHit_t *lastHit = NULL, *curHit = NULL;
|
|
|
|
for (i = 0; i < MAX_DETHITS; i++)
|
|
{
|
|
if (0 == detHits[i].time)
|
|
{
|
|
// empty slot. add our player here.
|
|
detHits[i].detpack = det;
|
|
detHits[i].player = player;
|
|
detHits[i].time = level.time;
|
|
}
|
|
lastHit = &detHits[i];
|
|
}
|
|
// getting here means we've filled our list of detHits, so begin recycling them, starting with the oldest hit.
|
|
curHit = &detHits[0];
|
|
while (lastHit->time < curHit->time)
|
|
{
|
|
lastHit = curHit;
|
|
curHit++;
|
|
// just a safety check here
|
|
if (curHit == &detHits[0])
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
curHit->detpack = det;
|
|
curHit->player = player;
|
|
curHit->time = level.time;
|
|
}
|
|
|
|
void ClearThisDetpacksHits(gentity_t *det)
|
|
{
|
|
int i = 0;
|
|
|
|
for (i = 0; i < MAX_DETHITS; i++)
|
|
{
|
|
if (detHits[i].detpack == det)
|
|
{
|
|
detHits[i].player = NULL;
|
|
detHits[i].detpack = NULL;
|
|
detHits[i].time = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void DetpackBlammoThink(gentity_t *ent)
|
|
{
|
|
int i = 0, lifetime = 3000; // how long (in msec) the shockwave lasts
|
|
int knockback = 400;
|
|
float curBlastRadius = 50.0*ent->count, radius = 0;
|
|
vec3_t vTemp;
|
|
trace_t tr;
|
|
gentity_t *pl = NULL;
|
|
|
|
if (ent->count++ > (lifetime*0.01))
|
|
{
|
|
ClearThisDetpacksHits(ent);
|
|
G_FreeEntity(ent);
|
|
return;
|
|
}
|
|
|
|
// get a list of players within the blast radius at this time.
|
|
// only hit the ones we can see from the center of the explosion.
|
|
for (i=0; i<MAX_CLIENTS; i++)
|
|
{
|
|
if ( g_entities[i].client && g_entities[i].takedamage)
|
|
{
|
|
pl = &g_entities[i];
|
|
VectorSubtract(pl->s.pos.trBase, ent->s.origin, vTemp);
|
|
radius = VectorNormalize(vTemp);
|
|
if ( (radius <= curBlastRadius) && !PlayerHitByDet(ent, pl) )
|
|
{
|
|
// within the proper radius. do we have LOS to the player?
|
|
trap_Trace (&tr, ent->s.origin, NULL, NULL, pl->s.pos.trBase, ent->s.number, MASK_SHOT );
|
|
if (tr.entityNum == i)
|
|
{
|
|
// oh yeah. you're gettin' hit.
|
|
AddPlayerToDetHits(ent, pl);
|
|
VectorMA(pl->client->ps.velocity, knockback, vTemp, pl->client->ps.velocity);
|
|
// make sure the player goes up some
|
|
if (pl->client->ps.velocity[2] < 100)
|
|
{
|
|
pl->client->ps.velocity[2] = 100;
|
|
}
|
|
if ( !pl->client->ps.pm_time )
|
|
{
|
|
int t;
|
|
|
|
t = knockback * 2;
|
|
if ( t < 50 ) {
|
|
t = 50;
|
|
}
|
|
if ( t > 200 ) {
|
|
t = 200;
|
|
}
|
|
pl->client->ps.pm_time = t;
|
|
pl->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
|
|
}
|
|
}
|
|
}
|
|
// this was just for testing. looks pretty neat, though.
|
|
/*
|
|
else
|
|
{
|
|
VectorMA(ent->s.origin, curBlastRadius, vTemp, vTemp);
|
|
G_TempEntity(vTemp, EV_FX_STEAM);
|
|
}
|
|
*/
|
|
}
|
|
}
|
|
|
|
ent->nextthink = level.time + FRAMETIME;
|
|
}
|
|
|
|
void DetonateDetpack(gentity_t *ent)
|
|
{
|
|
// find all detpacks. the one whose parent is ent...blow up
|
|
gentity_t *detpack = NULL;
|
|
char *classname = BG_FindClassnameForHoldable(HI_DETPACK);
|
|
|
|
if (!classname)
|
|
{
|
|
return;
|
|
}
|
|
while ((detpack = G_Find (detpack, FOFS(classname), classname)) != NULL)
|
|
{
|
|
if (detpack->parent == ent)
|
|
{
|
|
// found it. BLAMMO!
|
|
// play explosion sound to all clients
|
|
gentity_t *te = NULL;
|
|
|
|
te = G_TempEntity( detpack->s.pos.trBase, EV_GLOBAL_SOUND );
|
|
te->s.eventParm = G_SoundIndex( "sound/weapons/explosions/detpakexplode.wav" );//cgs.media.detpackExplodeSound
|
|
te->r.svFlags |= SVF_BROADCAST;
|
|
|
|
//so we can't be blown up by things we're blowing up
|
|
detpack->takedamage = 0;
|
|
|
|
G_AddEvent(detpack, EV_DETPACK, 0);
|
|
G_RadiusDamage( detpack->s.origin, detpack->parent, DETPACK_DAMAGE, DETPACK_RADIUS,
|
|
detpack, DAMAGE_HALF_NOTLOS|DAMAGE_ALL_TEAMS, MOD_DETPACK );
|
|
// just turn the model invisible and let the entity think for a bit to deliver a shockwave
|
|
//G_FreeEntity(detpack);
|
|
detpack->classname = NULL;
|
|
detpack->s.modelindex = 0;
|
|
detpack->think = DetpackBlammoThink;
|
|
detpack->count = 1;
|
|
detpack->nextthink = level.time + FRAMETIME;
|
|
return;
|
|
}
|
|
else if (detpack == ent) // if detpack == ent, we're blowing up this detpack cuz it's been sitting too long
|
|
{
|
|
detpack_shot(detpack, NULL, NULL, 0, 0);
|
|
return;
|
|
}
|
|
}
|
|
// hmm. couldn't find it.
|
|
detpack = NULL;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#define SHIELD_HEALTH 250
|
|
#define SHIELD_HEALTH_DEC 10 // 25 seconds
|
|
#define MAX_SHIELD_HEIGHT 254
|
|
#define MAX_SHIELD_HALFWIDTH 255
|
|
#define SHIELD_HALFTHICKNESS 4
|
|
#define SHIELD_PLACEDIST 64
|
|
|
|
|
|
static qhandle_t shieldAttachSound=0;
|
|
static qhandle_t shieldActivateSound=0;
|
|
static qhandle_t shieldDamageSound=0;
|
|
//RPG-X: - RedTechie Added shild ZAPZPZAAAAAAppPPP sound!
|
|
static qhandle_t shieldMurderSound=0;
|
|
|
|
|
|
void ShieldRemove(gentity_t *self)
|
|
{
|
|
self->think = G_FreeEntity;
|
|
self->nextthink = level.time + 100;
|
|
|
|
// Play raising sound...
|
|
G_AddEvent(self, EV_GENERAL_SOUND, shieldActivateSound);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
// Count down the health of the shield.
|
|
void ShieldThink(gentity_t *self)
|
|
{
|
|
self->s.eFlags &= ~(EF_ITEMPLACEHOLDER | EF_NODRAW);
|
|
// self->health -= SHIELD_HEALTH_DEC;
|
|
self->nextthink = level.time + 1000;
|
|
if (self->health <= 0)
|
|
{
|
|
ShieldRemove(self);
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
// The shield was damaged to below zero health.
|
|
void ShieldDie(gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod)
|
|
{
|
|
// Play damaging sound...
|
|
G_AddEvent(self, EV_GENERAL_SOUND, shieldDamageSound);
|
|
|
|
ShieldRemove(self);
|
|
}
|
|
|
|
|
|
// The shield had damage done to it. Make it flicker.
|
|
void ShieldPain(gentity_t *self, gentity_t *attacker, int damage)
|
|
{
|
|
// Set the itemplaceholder flag to indicate the the shield drawing that the shield pain should be drawn.
|
|
self->s.eFlags |= EF_ITEMPLACEHOLDER;
|
|
self->think = ShieldThink;
|
|
self->nextthink = level.time + 400;
|
|
|
|
// Play damaging sound...
|
|
G_AddEvent(self, EV_GENERAL_SOUND, shieldDamageSound);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
// Try to turn the shield back on after a delay.
|
|
void ShieldGoSolid(gentity_t *self)
|
|
{
|
|
trace_t tr;
|
|
//gentity_t *other;
|
|
//other = G_Spawn();
|
|
|
|
// see if we're valid
|
|
//self->health--;
|
|
if (self->health <= 0)
|
|
{
|
|
ShieldRemove(self);
|
|
return;
|
|
}
|
|
|
|
trap_Trace (&tr, self->r.currentOrigin, self->r.mins, self->r.maxs, self->r.currentOrigin, self->s.number, CONTENTS_BODY );
|
|
if(tr.startsolid)
|
|
{ // gah, we can't activate yet
|
|
self->nextthink = level.time + 200;
|
|
self->think = ShieldGoSolid;
|
|
trap_LinkEntity(self);
|
|
}
|
|
else
|
|
{ // get hard... huh-huh...
|
|
self->r.contents = CONTENTS_SOLID;
|
|
self->s.eFlags &= ~(/*EF_NODRAW | */EF_ITEMPLACEHOLDER);
|
|
self->nextthink = level.time + 1000;
|
|
self->think = ShieldThink;
|
|
self->takedamage = qfalse;//RPG-X: - RedTechie use to be qtrue
|
|
|
|
trap_LinkEntity(self);
|
|
|
|
|
|
// Play raising sound...
|
|
G_AddEvent(self, EV_GENERAL_SOUND, shieldActivateSound);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
// Turn the shield off to allow a friend to pass through.
|
|
//RPG-X J2J EDIT here:
|
|
void ShieldGoNotSolid(gentity_t *self)
|
|
{
|
|
// make the shield non-solid very briefly
|
|
self->r.contents = CONTENTS_NONE;
|
|
// self->s.eFlags |= EF_NODRAW; //Commenting this should make it look like the player passes through..
|
|
// nextthink needs to have a large enough interval to avoid excess accumulation of Activate messages
|
|
self->nextthink = level.time + 200;
|
|
self->think = ShieldGoSolid;
|
|
|
|
|
|
self->takedamage = qfalse;
|
|
trap_LinkEntity(self);
|
|
|
|
// Play raising sound...
|
|
G_AddEvent(self, EV_GENERAL_SOUND, shieldActivateSound);
|
|
}
|
|
|
|
|
|
// Somebody (a player) has touched the shield. See if it is a "friend".
|
|
void ShieldTouch(gentity_t *self, gentity_t *other, trace_t *trace)
|
|
{
|
|
|
|
/* if (g_gametype.integer >= GT_TEAM)
|
|
{ // let teammates through
|
|
// compare the parent's team to the "other's" team
|
|
if (( self->parent->client) && (other->client))
|
|
{
|
|
if ( self->s.otherEntityNum2 == other->client->sess.sessionTeam || other->client->sess.sessionClass == PC_ALPHAOMEGA22 )
|
|
{
|
|
ShieldGoNotSolid(self);
|
|
}
|
|
}
|
|
}
|
|
else*/
|
|
{//let the person who dropped the shield through
|
|
if (other->client->sess.sessionClass == PC_ADMIN )
|
|
{
|
|
ShieldGoNotSolid(self);
|
|
}
|
|
//RPG-X:J2J Damage for shields
|
|
else
|
|
{
|
|
other->flags |= EF_MOVER_STOP; //Attempt to not let non admins thru.
|
|
//RPG-X: RedTechie - Added code for zap sound also a cvar to control damage a non admin walks into if cvar is set to 0 disables health from being drained (happens every 1 second)
|
|
//if ( level.time >= level.message + 1000 ) {
|
|
// level.message = level.time;
|
|
G_AddEvent(self, EV_GENERAL_SOUND, shieldMurderSound);//RPG-X: RedTechie - ZAPtacular! sound to my ears
|
|
if(rpg_forcefielddamage.integer != 0){
|
|
if(rpg_forcefielddamage.integer > 999){
|
|
rpg_forcefielddamage.integer = 999;
|
|
}
|
|
other->health -= rpg_forcefielddamage.integer;
|
|
|
|
//RPG-X: RedTechie - Fixed free ent if medic revive on
|
|
if(rpg_medicsrevive.integer == 1){
|
|
if(other->health <= 1){
|
|
other->client->ps.stats[STAT_WEAPONS] = ( 1 << WP_NONE );
|
|
other->client->ps.stats[STAT_HOLDABLE_ITEM] = HI_NONE;
|
|
other->client->ps.stats[STAT_HEALTH] = other->health = 1;
|
|
player_die (other, other, other, 1, MOD_FORCEFIELD);
|
|
}
|
|
}else{
|
|
if(other->health <= 1){
|
|
other->client->ps.stats[STAT_HEALTH] = other->health = 0;
|
|
player_die (other, other, other, 100000, MOD_FORCEFIELD);
|
|
}
|
|
}
|
|
}
|
|
//}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
// After a short delay, create the shield by expanding in all directions.
|
|
void CreateShield(gentity_t *ent)
|
|
{
|
|
trace_t tr;
|
|
vec3_t mins, maxs, end, posTraceEnd, negTraceEnd, start;
|
|
int height, posWidth, negWidth, halfWidth = 0;
|
|
qboolean xaxis;
|
|
int paramData = 0;
|
|
static int shieldID;
|
|
|
|
// trace upward to find height of shield
|
|
VectorCopy(ent->r.currentOrigin, end);
|
|
end[2] += MAX_SHIELD_HEIGHT;
|
|
trap_Trace (&tr, ent->r.currentOrigin, NULL, NULL, end, ent->s.number, MASK_SHOT );
|
|
height = (int)(MAX_SHIELD_HEIGHT * tr.fraction);
|
|
|
|
// use angles to find the proper axis along which to align the shield
|
|
VectorSet(mins, -SHIELD_HALFTHICKNESS, -SHIELD_HALFTHICKNESS, 0);
|
|
VectorSet(maxs, SHIELD_HALFTHICKNESS, SHIELD_HALFTHICKNESS, height);
|
|
VectorCopy(ent->r.currentOrigin, posTraceEnd);
|
|
VectorCopy(ent->r.currentOrigin, negTraceEnd);
|
|
|
|
if ((int)(ent->s.angles[YAW]) == 0) // shield runs along y-axis
|
|
{
|
|
posTraceEnd[1]+=MAX_SHIELD_HALFWIDTH;
|
|
negTraceEnd[1]-=MAX_SHIELD_HALFWIDTH;
|
|
xaxis = qfalse;
|
|
}
|
|
else // shield runs along x-axis
|
|
{
|
|
posTraceEnd[0]+=MAX_SHIELD_HALFWIDTH;
|
|
negTraceEnd[0]-=MAX_SHIELD_HALFWIDTH;
|
|
xaxis = qtrue;
|
|
}
|
|
|
|
// trace horizontally to find extend of shield
|
|
// positive trace
|
|
VectorCopy(ent->r.currentOrigin, start);
|
|
start[2] += (height>>1);
|
|
trap_Trace (&tr, start, 0, 0, posTraceEnd, ent->s.number, MASK_SHOT );
|
|
posWidth = MAX_SHIELD_HALFWIDTH * tr.fraction;
|
|
// negative trace
|
|
trap_Trace (&tr, start, 0, 0, negTraceEnd, ent->s.number, MASK_SHOT );
|
|
negWidth = MAX_SHIELD_HALFWIDTH * tr.fraction;
|
|
|
|
// kef -- monkey with dimensions and place origin in center
|
|
halfWidth = (posWidth + negWidth)>>1;
|
|
if (xaxis)
|
|
{
|
|
ent->r.currentOrigin[0] = ent->r.currentOrigin[0] - negWidth + halfWidth;
|
|
}
|
|
else
|
|
{
|
|
ent->r.currentOrigin[1] = ent->r.currentOrigin[1] - negWidth + halfWidth;
|
|
}
|
|
ent->r.currentOrigin[2] += (height>>1);
|
|
|
|
// set entity's mins and maxs to new values, make it solid, and link it
|
|
if (xaxis)
|
|
{
|
|
VectorSet(ent->r.mins, -halfWidth, -SHIELD_HALFTHICKNESS, -(height>>1));
|
|
VectorSet(ent->r.maxs, halfWidth, SHIELD_HALFTHICKNESS, height>>1);
|
|
}
|
|
else
|
|
{
|
|
VectorSet(ent->r.mins, -SHIELD_HALFTHICKNESS, -halfWidth, -(height>>1));
|
|
VectorSet(ent->r.maxs, SHIELD_HALFTHICKNESS, halfWidth, height>>1);
|
|
}
|
|
ent->clipmask = MASK_SHOT;
|
|
|
|
// Information for shield rendering.
|
|
|
|
// xaxis - 1 bit
|
|
// height - 0-254 8 bits
|
|
// posWidth - 0-255 8 bits
|
|
// negWidth - 0 - 255 8 bits
|
|
|
|
paramData = (xaxis << 24) | (height << 16) | (posWidth << 8) | (negWidth);
|
|
ent->s.time2 = paramData;
|
|
|
|
if ( ent->s.otherEntityNum2 == TEAM_RED )
|
|
{
|
|
ent->team = "1";
|
|
}
|
|
else if ( ent->s.otherEntityNum2 == TEAM_BLUE )
|
|
{
|
|
ent->team = "2";
|
|
}
|
|
|
|
//RPG-X: - RedTechie no shield count down
|
|
if ( g_pModSpecialties.integer != 0 )
|
|
{
|
|
//ent->health = ceil(SHIELD_HEALTH*4*g_dmgmult.value);
|
|
}
|
|
else
|
|
{
|
|
ent->health = ceil(SHIELD_HEALTH*g_dmgmult.value);
|
|
}
|
|
|
|
|
|
ent->s.time = ent->health;//???
|
|
ent->pain = ShieldPain;
|
|
ent->die = ShieldDie;
|
|
ent->touch = ShieldTouch;
|
|
|
|
ent->r.svFlags |= SVF_SHIELD_BBOX;
|
|
|
|
// see if we're valid
|
|
trap_Trace (&tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, ent->r.currentOrigin, ent->s.number, CONTENTS_BODY );
|
|
|
|
if (tr.startsolid)
|
|
{ // Something in the way!
|
|
// make the shield non-solid very briefly
|
|
ent->r.contents = CONTENTS_NONE;
|
|
ent->s.eFlags |= EF_NODRAW;
|
|
// nextthink needs to have a large enough interval to avoid excess accumulation of Activate messages
|
|
ent->nextthink = level.time + 200;
|
|
ent->think = ShieldGoSolid;
|
|
ent->takedamage = qfalse;
|
|
trap_LinkEntity(ent);
|
|
}
|
|
else
|
|
{ // Get solid.
|
|
ent->r.contents = CONTENTS_PLAYERCLIP|CONTENTS_SHOTCLIP;//CONTENTS_SOLID;
|
|
|
|
ent->nextthink = level.time + 1000;
|
|
ent->think = ShieldThink;
|
|
|
|
ent->takedamage = qfalse;//RPG-X: - RedTechie Use to be qtrue
|
|
trap_LinkEntity(ent);
|
|
|
|
// Play raising sound...
|
|
G_AddEvent(ent, EV_GENERAL_SOUND, shieldActivateSound);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
qboolean PlaceShield(gentity_t *playerent)
|
|
{
|
|
static const gitem_t *shieldItem = NULL;
|
|
gentity_t *shield = NULL;
|
|
trace_t tr;
|
|
vec3_t fwd, pos, dest, mins = {-16,-16, 0}, maxs = {16,16,16};
|
|
|
|
if (shieldAttachSound==0)
|
|
{
|
|
shieldAttachSound = G_SoundIndex("sound/weapons/detpacklatch.wav");
|
|
shieldActivateSound = G_SoundIndex("sound/movers/forceup.wav");
|
|
shieldDamageSound = G_SoundIndex("sound/ambience/spark5.wav");
|
|
shieldMurderSound = G_SoundIndex("sound/world/electro.wav"); //RPG-X: - RedTechie Added shild ZAP! sound
|
|
shieldItem = BG_FindItemForHoldable(HI_SHIELD);
|
|
}
|
|
|
|
// can we place this in front of us?
|
|
AngleVectors (playerent->client->ps.viewangles, fwd, NULL, NULL);
|
|
fwd[2] = 0;
|
|
VectorMA(playerent->client->ps.origin, SHIELD_PLACEDIST, fwd, dest);
|
|
trap_Trace (&tr, playerent->client->ps.origin, mins, maxs, dest, playerent->s.number, MASK_SHOT );
|
|
if (tr.fraction > 0.9)
|
|
{//room in front
|
|
VectorCopy(tr.endpos, pos);
|
|
// drop to floor
|
|
VectorSet( dest, pos[0], pos[1], pos[2] - 4096 );
|
|
trap_Trace( &tr, pos, mins, maxs, dest, playerent->s.number, MASK_SOLID );
|
|
if ( !tr.startsolid && !tr.allsolid )
|
|
{
|
|
// got enough room so place the portable shield
|
|
shield = G_Spawn();
|
|
|
|
// Figure out what direction the shield is facing.
|
|
if (fabs(fwd[0]) > fabs(fwd[1]))
|
|
{ // shield is north/south, facing east.
|
|
shield->s.angles[YAW] = 0;
|
|
}
|
|
else
|
|
{ // shield is along the east/west axis, facing north
|
|
shield->s.angles[YAW] = 90;
|
|
}
|
|
shield->think = CreateShield;
|
|
shield->nextthink = level.time + 500; // power up after .5 seconds
|
|
shield->parent = playerent;
|
|
|
|
// Set team number.
|
|
shield->s.otherEntityNum2 = playerent->client->sess.sessionTeam;
|
|
|
|
shield->s.eType = ET_USEABLE;
|
|
shield->s.modelindex = HI_SHIELD; // this'll be used in CG_Useable() for rendering.
|
|
shield->classname = shieldItem->classname;
|
|
|
|
shield->r.contents = CONTENTS_TRIGGER;
|
|
|
|
shield->touch = 0;
|
|
// using an item causes it to respawn
|
|
shield->use = 0; //Use_Item;
|
|
|
|
// allow to ride movers
|
|
shield->s.groundEntityNum = tr.entityNum;
|
|
|
|
G_SetOrigin( shield, tr.endpos );
|
|
|
|
shield->s.eFlags &= ~EF_NODRAW;
|
|
shield->r.svFlags &= ~SVF_NOCLIENT;
|
|
|
|
trap_LinkEntity (shield);
|
|
|
|
// Play placing sound...
|
|
G_AddEvent(shield, EV_GENERAL_SOUND, shieldAttachSound);
|
|
|
|
return qtrue;
|
|
}
|
|
}
|
|
// no room
|
|
return qfalse;
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------------------------------- DECOY ACTIVITIES
|
|
void DecoyThink(gentity_t *ent)
|
|
{
|
|
ent->s.apos =(ent->parent)->s.apos; // Update Current Rotation
|
|
ent->nextthink = level.time + irandom(2000, 6000); // Next think between 2 & 8 seconds
|
|
|
|
(ent->count) --; // Count Down
|
|
if (ent->count<0) G_FreeEntity(ent); // Time To Erase The Ent
|
|
}
|
|
|
|
qboolean PlaceDecoy(gentity_t *ent)
|
|
{
|
|
gentity_t *decoy = NULL;
|
|
static gitem_t *decoyItem = NULL;
|
|
float detDistance = 80; // Distance the object will be placed from player
|
|
trace_t tr;
|
|
vec3_t fwd, right, up, end, mins = {-16,-16,-24}, maxs = {16,16,16};
|
|
|
|
if (decoyItem == NULL)
|
|
{
|
|
decoyItem = BG_FindItemForHoldable(HI_DECOY);
|
|
}
|
|
|
|
// can we place this in front of us?
|
|
AngleVectors (ent->client->ps.viewangles, fwd, right, up);
|
|
fwd[2] = 0;
|
|
VectorMA(ent->client->ps.origin, detDistance, fwd, end);
|
|
trap_Trace (&tr, ent->client->ps.origin, mins, maxs, end, ent->s.number, MASK_SHOT );
|
|
if ( !tr.allsolid && !tr.startsolid && tr.fraction > 0.9 )
|
|
{
|
|
//--------------------------- SPAWN AND PLACE DECOY ON GROUND
|
|
decoy = G_Spawn();
|
|
G_SpawnItem(decoy, decoyItem); // Generate it as an item, temporarly
|
|
decoy->physicsBounce = 0.0f;//decoys are *not* bouncy
|
|
VectorMA(ent->client->ps.origin, detDistance + mins[0], fwd, decoy->s.origin);
|
|
decoy->r.mins[2] = mins[2];//keep it off the floor
|
|
VectorNegate(fwd, fwd); // ??? What does this do??
|
|
vectoangles(fwd, decoy->s.angles);
|
|
if ( !FinishSpawningDecoy(decoy, decoyItem - bg_itemlist) )
|
|
{
|
|
return qfalse; // Drop to ground and trap to clients
|
|
}
|
|
decoy->s.clientNum = ent->client->ps.clientNum;
|
|
|
|
//--------------------------- SPECIALIZED DECOY SETUP
|
|
decoy->think = DecoyThink;
|
|
decoy->count = 12; // about 1 minute before dissapear
|
|
decoy->nextthink = level.time + 4000; // think after 4 seconds
|
|
decoy->parent = ent;
|
|
|
|
(decoy->s).eType = (ent->s).eType; // set to type PLAYER
|
|
(decoy->s).eFlags= (ent->s).eFlags;
|
|
(decoy->s).eFlags |= EF_ITEMPLACEHOLDER;// set the HOLOGRAM FLAG to ON
|
|
|
|
decoy->s.weapon = ent->s.weapon; // get Player's Wepon Type
|
|
// decoy->s.constantLight = 10 + (10 << 8) + (10 << 16) + (9 << 24);
|
|
|
|
//decoy->s.pos.trBase[2] += 24; // shift up to adjust origin of body
|
|
decoy->s.apos = ent->s.apos; // copy angle of player to decoy
|
|
|
|
decoy->s.legsAnim = BOTH_STAND1; // Just standing TORSO_STAND
|
|
decoy->s.torsoAnim = BOTH_STAND1;
|
|
|
|
//--------------------------- WEAPON ADJUST
|
|
if (decoy->s.weapon==WP_PHASER || decoy->s.weapon==WP_DERMAL_REGEN)
|
|
decoy->s.weapon = WP_COMPRESSION_RIFLE;
|
|
|
|
// The Phaser and Dreadnought (Arc Welder) weapons are rendered on the
|
|
// client side differently, and cannot be used by the decoy
|
|
|
|
return qtrue; // SUCCESS
|
|
}
|
|
else
|
|
{
|
|
return qfalse; // FAILURE: no room
|
|
}
|
|
}
|
|
//-------------------------------------------------------------- DECOY ACTIVITIES
|
|
|
|
|
|
|
|
|
|
|
|
void G_Rematerialize( gentity_t *ent )
|
|
{
|
|
if ( ent->s.number == borgQueenClientNum )
|
|
{
|
|
ent->client->teleportTime = level.time + ( 60 * 1000 );
|
|
ent->client->ps.stats[STAT_USEABLE_PLACED] = 60;
|
|
}
|
|
else
|
|
{
|
|
ent->client->teleportTime = level.time + ( 15 * 1000 );
|
|
ent->client->ps.stats[STAT_USEABLE_PLACED] = 15;
|
|
}
|
|
ent->flags &= ~FL_NOTARGET;
|
|
ent->takedamage = qtrue;
|
|
ent->r.contents = MASK_PLAYERSOLID;
|
|
ent->s.eFlags &= ~EF_NODRAW;
|
|
ent->client->ps.eFlags &= ~EF_NODRAW;
|
|
TeleportPlayer( ent, ent->client->ps.origin, ent->client->ps.viewangles, TP_BORG );
|
|
//ent->client->ps.stats[STAT_USEABLE_PLACED] = 0;
|
|
//take it away
|
|
ent->client->ps.stats[STAT_HOLDABLE_ITEM] = 0;
|
|
}
|
|
|
|
void G_GiveHoldable( gclient_t *client, holdable_t item )
|
|
{
|
|
gitem_t *holdable = BG_FindItemForHoldable( item );
|
|
|
|
client->ps.stats[STAT_HOLDABLE_ITEM] = holdable - bg_itemlist;//teleport spots should be on other side of map
|
|
RegisterItem( holdable );
|
|
}
|
|
|
|
/*
|
|
================
|
|
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;
|
|
|
|
client = ent->client;
|
|
|
|
if ( oldEventSequence < client->ps.eventSequence - MAX_PS_EVENTS ) {
|
|
oldEventSequence = client->ps.eventSequence - MAX_PS_EVENTS;
|
|
}
|
|
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:
|
|
if ( ent->s.eType != ET_PLAYER ) {
|
|
break; // not in the player model
|
|
}
|
|
if ( g_dmflags.integer & DF_NO_FALLING ) {
|
|
break;
|
|
}
|
|
if ( rpg_selfdamage.integer != 0 )
|
|
{
|
|
if ( event == EV_FALL_FAR )
|
|
{
|
|
damage = 110; //10 -TiM : Make the falling more realistc!
|
|
}
|
|
else
|
|
{
|
|
damage = 90; //5
|
|
}
|
|
}
|
|
else
|
|
{
|
|
damage = 0;
|
|
}
|
|
ent->pain_debounce_time = level.time + 200; // no normal pain sound
|
|
G_Damage (ent, NULL, NULL, NULL, NULL, damage, DAMAGE_ARMOR_PIERCING, MOD_FALLING);
|
|
|
|
break;
|
|
|
|
case EV_FIRE_WEAPON:
|
|
FireWeapon( ent, qfalse );
|
|
break;
|
|
|
|
case EV_ALT_FIRE:
|
|
FireWeapon( ent, qtrue );
|
|
break;
|
|
|
|
case EV_FIRE_EMPTY_PHASER:
|
|
FireWeapon( ent, qfalse );
|
|
break;
|
|
|
|
case EV_USE_ITEM1: // transporter
|
|
|
|
//------------------------------------------------------- DROP FLAGS
|
|
if ( ent->client->ps.powerups[PW_REDFLAG] ) {
|
|
Team_ReturnFlag(TEAM_RED);
|
|
}
|
|
else if ( ent->client->ps.powerups[PW_BLUEFLAG] ) {
|
|
Team_ReturnFlag(TEAM_BLUE);
|
|
}
|
|
ent->client->ps.powerups[PW_BLUEFLAG] = 0;
|
|
ent->client->ps.powerups[PW_REDFLAG] = 0;
|
|
//------------------------------------------------------- DROP FLAGS
|
|
if ( ent->client->sess.sessionClass == PC_BORG )
|
|
{
|
|
if ( !ent->client->ps.stats[STAT_USEABLE_PLACED] )
|
|
{//go into free-roaming mode
|
|
gentity_t *tent;
|
|
//FIXME: limit this to 10 seconds
|
|
ent->flags |= FL_NOTARGET;
|
|
ent->takedamage = qfalse;
|
|
ent->r.contents = CONTENTS_PLAYERCLIP;
|
|
ent->s.eFlags |= EF_NODRAW;
|
|
ent->client->ps.eFlags |= EF_NODRAW;
|
|
ent->client->ps.stats[STAT_USEABLE_PLACED] = 2;
|
|
ent->client->teleportTime = level.time + 10000;
|
|
tent = G_TempEntity( ent->client->ps.origin, EV_BORG_TELEPORT );
|
|
tent->s.clientNum = ent->s.clientNum;
|
|
}
|
|
else
|
|
{//come out of free-roaming mode, teleport to current position
|
|
G_Rematerialize( ent );
|
|
}
|
|
}
|
|
else// get rid of transporter and go to random spawnpoint
|
|
{
|
|
//vec3_t origin, angles;
|
|
//gentity_t *tent;
|
|
|
|
//SelectSpawnPoint( ent->client->ps.origin, origin, angles );
|
|
|
|
//if(TransDat[ent->client->ps.clientNum].pUsed == qfalse)
|
|
//TiM: Since we purge the vectors each cycle. I'll save us some memory by using the vectors themselves as a check.
|
|
if ( TransDat[ent->client->ps.clientNum].beamTime == 0 &&
|
|
VectorCompare( vec3_origin, TransDat[ent->client->ps.clientNum].storedCoord[TPT_PORTABLE].origin ) &&
|
|
VectorCompare( vec3_origin, TransDat[ent->client->ps.clientNum].storedCoord[TPT_PORTABLE].angles ) )
|
|
{
|
|
VectorCopy( ent->client->ps.origin, TransDat[ent->client->ps.clientNum].storedCoord[TPT_PORTABLE].origin );
|
|
VectorCopy( ent->client->ps.viewangles, TransDat[ent->client->ps.clientNum].storedCoord[TPT_PORTABLE].angles );
|
|
//VectorCopy(ent->client->ps.origin, TransDat[ent->client->ps.clientNum].pTransCoord);
|
|
//VectorCopy(ent->client->ps.viewangles, TransDat[ent->client->ps.clientNum].pTransCoordRot);
|
|
//TransDat[ent->client->ps.clientNum].pUsed = qtrue;
|
|
trap_SendServerCommand( ent-g_entities, va("chat \"Site to Site Transporter Location Confirmed.\nPress again to Energize.\"", Q_COLOR_ESCAPE));
|
|
//trap_SendConsoleCommand( EXEC_APPEND, va("echo Site to Site Transporter Location Confirmed.\necho Press again to Energize.") );
|
|
ent->client->ps.stats[STAT_HOLDABLE_ITEM] = BG_FindItemForHoldable( HI_TRANSPORTER ) - bg_itemlist;
|
|
ent->client->ps.stats[STAT_USEABLE_PLACED] = 2; // = 1
|
|
break;
|
|
}
|
|
|
|
//TeleportPlayer( ent, TransDat[ent->client->ps.clientNum].pTransCoord, TransDat[ent->client->ps.clientNum].pTransCoordRot, TP_NORMAL );
|
|
if ( TransDat[ent->client->ps.clientNum].beamTime == 0 && level.time > ent->client->ps.powerups[PW_QUAD] ) {
|
|
G_InitTransport( ent->client->ps.clientNum, TransDat[ent->client->ps.clientNum].storedCoord[TPT_PORTABLE].origin,
|
|
TransDat[ent->client->ps.clientNum].storedCoord[TPT_PORTABLE].angles );
|
|
|
|
memset( &TransDat[ent->client->ps.clientNum].storedCoord[TPT_PORTABLE], 0, sizeof( TransDat[ent->client->ps.clientNum].storedCoord[TPT_PORTABLE]) );
|
|
}
|
|
else {
|
|
trap_SendServerCommand( ent-g_entities, va("chat \"Unable to comply. Already within transport cycle.\"", Q_COLOR_ESCAPE));
|
|
}
|
|
|
|
ent->client->ps.stats[STAT_USEABLE_PLACED] = 0;
|
|
|
|
if ( ent->client->sess.sessionClass == PC_ALPHAOMEGA22 )
|
|
{
|
|
ent->client->teleportTime = level.time + ( 1 * 1000 ); // 15 * 1000
|
|
ent->client->ps.stats[STAT_USEABLE_PLACED] = 1; // = 1
|
|
}
|
|
|
|
}
|
|
break;
|
|
|
|
case EV_USE_ITEM2: // medkit
|
|
// New set of rules. You get either 100 health, or an extra 25, whichever is higher.
|
|
// Give 1/4 health.
|
|
ent->health += ent->client->ps.stats[STAT_MAX_HEALTH]*0.25;
|
|
|
|
if (ent->health < ent->client->ps.stats[STAT_MAX_HEALTH])
|
|
{ // If that doesn't bring us up to 100, make it go up to 100.
|
|
ent->health = ent->client->ps.stats[STAT_MAX_HEALTH];
|
|
}
|
|
else if (ent->health > ent->client->ps.stats[STAT_MAX_HEALTH]*2)
|
|
{ // Otherwise, 25 is all you get. Just make sure we don't go above 200.
|
|
ent->health = ent->client->ps.stats[STAT_MAX_HEALTH]*2;
|
|
}
|
|
break;
|
|
|
|
case EV_USE_ITEM3: // detpack
|
|
// if we haven't placed it yet, place it
|
|
if (0 == ent->client->ps.stats[STAT_USEABLE_PLACED])
|
|
{
|
|
if ( PlaceDetpack(ent) )
|
|
{
|
|
ent->client->ps.stats[STAT_USEABLE_PLACED] = 1;
|
|
trap_SendServerCommand( ent-g_entities, "cp \"CHARGE PLACED\"" );
|
|
}
|
|
else
|
|
{//couldn't place it
|
|
ent->client->ps.stats[STAT_HOLDABLE_ITEM] = (BG_FindItemForHoldable( HI_DETPACK ) - bg_itemlist);
|
|
trap_SendServerCommand( ent-g_entities, "cp \"NO ROOM TO PLACE CHARGE\"" );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//turn off invincibility since you are going to do massive damage
|
|
//ent->client->ps.powerups[PW_GHOST] = 0;//NOTE: this means he can respawn and get a detpack 10 seconds later
|
|
// ok, we placed it earlier. blow it up.
|
|
ent->client->ps.stats[STAT_USEABLE_PLACED] = 0;
|
|
DetonateDetpack(ent);
|
|
}
|
|
break;
|
|
|
|
case EV_USE_ITEM4: // portable shield
|
|
if ( !PlaceShield(ent) ) // fixme if we fail, perhaps just spawn it as a pickup
|
|
{//couldn't place it
|
|
ent->client->ps.stats[STAT_HOLDABLE_ITEM] = (BG_FindItemForHoldable( HI_SHIELD ) - bg_itemlist);
|
|
trap_SendServerCommand( ent-g_entities, "cp \"NO ROOM TO PLACE FORCE FIELD\"" );
|
|
}
|
|
else
|
|
{
|
|
trap_SendServerCommand( ent-g_entities, "cp \"FORCE FIELD PLACED\"" );
|
|
//if ( ent->client->sess.sessionClass == PC_ADMIN )
|
|
// {
|
|
// ent->client->teleportTime = level.time + ( 30 * 1000 );
|
|
// ent->client->ps.stats[STAT_USEABLE_PLACED] = 30;
|
|
//}
|
|
}
|
|
break;
|
|
|
|
case EV_USE_ITEM5: // decoy
|
|
if ( !PlaceDecoy(ent) )
|
|
{//couldn't place it
|
|
ent->client->ps.stats[STAT_HOLDABLE_ITEM] = (BG_FindItemForHoldable( HI_DECOY ) - bg_itemlist);
|
|
trap_SendServerCommand( ent-g_entities, "cp \"NO ROOM TO PLACE DECOY\"" );
|
|
}
|
|
else
|
|
{
|
|
trap_SendServerCommand( ent-g_entities, "cp \"DECOY PLACED\"" );
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void BotTestSolid(vec3_t origin);
|
|
|
|
/*
|
|
==============
|
|
SendPendingPredictableEvents
|
|
==============
|
|
*/
|
|
void SendPendingPredictableEvents( playerState_t *ps ) {
|
|
gentity_t *t;
|
|
int event, seq;
|
|
int extEvent;
|
|
|
|
// if there are still events pending
|
|
if ( ps->entityEventSequence < ps->eventSequence ) {
|
|
// create a temporary entity for this event which is sent to everyone
|
|
// except the client generated the event
|
|
seq = ps->entityEventSequence & (MAX_PS_EVENTS-1);
|
|
event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 );
|
|
// set external event to zero before calling BG_PlayerStateToEntityState
|
|
extEvent = ps->externalEvent;
|
|
ps->externalEvent = 0;
|
|
// create temporary entity for event
|
|
t = G_TempEntity( ps->origin, event );
|
|
BG_PlayerStateToEntityState( ps, &t->s, qtrue );
|
|
t->s.eType = ET_EVENTS + event;
|
|
// send to everyone except the client who generated the event
|
|
t->r.svFlags |= SVF_NOTSINGLECLIENT;
|
|
t->r.singleClient = ps->clientNum;
|
|
// set back external event
|
|
ps->externalEvent = extEvent;
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============
|
|
ClientThink
|
|
|
|
This will be called once for each client frame, which will
|
|
usually be a couple times for each server frame on fast clients.
|
|
|
|
If "g_synchronousClients 1" is set, this will be called exactly
|
|
once for each server frame, which makes for smooth demo recording.
|
|
==============
|
|
*/
|
|
void ClientThink_real( gentity_t *ent ) {
|
|
gclient_t *client;
|
|
pmove_t pm;
|
|
vec3_t oldOrigin;
|
|
int oldEventSequence;
|
|
int msec;
|
|
usercmd_t *ucmd;
|
|
|
|
client = ent->client;
|
|
|
|
// don't think if the client is not yet connected (and thus not yet spawned in)
|
|
if (client->pers.connected != CON_CONNECTED) {
|
|
return;
|
|
}
|
|
// mark the time, so the connection sprite can be removed
|
|
ucmd = &ent->client->pers.cmd;
|
|
|
|
// sanity check the command time to prevent speedup cheating
|
|
if ( ucmd->serverTime > level.time + 200 ) {
|
|
ucmd->serverTime = level.time + 200;
|
|
// G_Printf("serverTime <<<<<\n" );
|
|
}
|
|
if ( ucmd->serverTime < level.time - 1000 ) {
|
|
ucmd->serverTime = level.time - 1000;
|
|
// G_Printf("serverTime >>>>>\n" );
|
|
}
|
|
|
|
msec = ucmd->serverTime - client->ps.commandTime;
|
|
// following others may result in bad times, but we still want
|
|
// to check for follow toggles
|
|
if ( msec < 1 && client->sess.spectatorState != SPECTATOR_FOLLOW ) {
|
|
return;
|
|
}
|
|
if ( msec > 200 ) {
|
|
msec = 200;
|
|
}
|
|
|
|
//
|
|
// check for exiting intermission
|
|
//
|
|
if ( level.intermissiontime ) {
|
|
ClientIntermissionThink( client );
|
|
return;
|
|
}
|
|
|
|
// Don't move while under intro sequence.
|
|
/*if (client->ps.introTime > level.time)
|
|
{ // Don't be visible either.
|
|
ent->s.eFlags |= EF_NODRAW;
|
|
SetClientViewAngle( ent, ent->s.angles );
|
|
ucmd->buttons = 0;
|
|
ucmd->weapon = 0;
|
|
// ucmd->angles[0] = ucmd->angles[1] = ucmd->angles[2] = 0;
|
|
ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0;
|
|
// return;
|
|
}*/
|
|
|
|
// spectators don't do much
|
|
if ( client->sess.sessionTeam == TEAM_SPECTATOR || (client->ps.eFlags&EF_ELIMINATED) ) {
|
|
if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) {
|
|
return;
|
|
}
|
|
SpectatorThink( ent, ucmd );
|
|
return;
|
|
}
|
|
|
|
// check for inactivity timer, but never drop the local client of a non-dedicated server
|
|
if ( !ClientInactivityTimer( client ) ) {
|
|
return;
|
|
}
|
|
|
|
//TiM - If we're null content... see what's up
|
|
if ( ent->r.contents == CONTENTS_NONE ) {
|
|
if ( !G_MoveBox( ent ) && client->ps.stats[STAT_HEALTH] >= 1 ) {
|
|
ent->r.contents = CONTENTS_BODY;
|
|
}
|
|
else {
|
|
ent->r.contents = CONTENTS_NONE ;
|
|
}
|
|
}
|
|
|
|
// clear the rewards if time
|
|
/*if ( level.time > client->rewardTime ) {
|
|
client->ps.eFlags &= ~EF_AWARD_MASK;
|
|
}*/
|
|
|
|
//RPG-X: Checked to see if medics revive is on if so do as following
|
|
if(rpg_medicsrevive.integer == 1){
|
|
if ( client->noclip ) {
|
|
client->ps.pm_type = PM_NOCLIP;
|
|
} else if ( client->ps.stats[STAT_HEALTH] == 1 ) {
|
|
client->ps.pm_type = PM_DEAD;
|
|
} else {
|
|
client->ps.pm_type = PM_NORMAL;
|
|
}
|
|
}else{
|
|
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;
|
|
}
|
|
}
|
|
|
|
//RPG-X: J2J & Phenix - For the gravity ent
|
|
if(client->SpecialGrav != qtrue)
|
|
{
|
|
client->ps.gravity = g_gravity.value;
|
|
}
|
|
|
|
// set speed
|
|
client->ps.speed = g_speed.value;
|
|
|
|
if ( client->ps.powerups[PW_HASTE] )
|
|
{
|
|
client->ps.speed *= 1.5;
|
|
}
|
|
else if (client->ps.powerups[PW_FLIGHT] )
|
|
{//flying around
|
|
client->ps.speed *= 1.3;
|
|
}
|
|
else if ( client->ps.stats[STAT_HEALTH] <= 20 ) {
|
|
client->ps.speed *= 0.55;
|
|
}
|
|
|
|
if (( client->ps.powerups[PW_EVOSUIT] ) && ( client->ps.gravity == 0 ))
|
|
{//Evosuit time.. RPG-X | Phenix | 8/8/2004
|
|
client->ps.speed *= 1.3;
|
|
}
|
|
|
|
//RPG-X: Redtechie - n00bie stay.....good boy!
|
|
if ( client->sess.sessionClass == PC_N00B ){
|
|
client->ps.speed = 0;
|
|
}
|
|
|
|
if ( client->sess.sessionClass == PC_HEAVY )
|
|
{
|
|
client->ps.speed *= 0.75;
|
|
}
|
|
|
|
if ( client->sess.sessionClass == PC_BORG )
|
|
{
|
|
if ( BG_BorgTransporting( &client->ps ) )
|
|
{
|
|
client->ps.speed *= 1.5;
|
|
}
|
|
else if ( !BG_BorgTransporting( &client->ps ) )
|
|
{
|
|
client->ps.speed *= 0.75;
|
|
}
|
|
}
|
|
|
|
//TiM : SP Style Transporter. :)
|
|
//first check to see if we should be beaming
|
|
if ( level.time < TransDat[client->ps.clientNum].beamTime ) {
|
|
//Hold players in place. Do we need??
|
|
//client->ps.speed = 0.0;
|
|
|
|
//if we're past the mid point of each materialization cycle, make it
|
|
//so bullets and other players will pass thru the transportee. :)
|
|
if ( (level.time > TransDat[client->ps.clientNum].beamTime - 6000) &&
|
|
( level.time < TransDat[client->ps.clientNum].beamTime - 2000 ) ) {
|
|
if ( client->ps.stats[STAT_HEALTH] > 1 ) {
|
|
ent->r.contents = CONTENTS_NONE;
|
|
}
|
|
//ent->r.contents = CONTENTS_CORPSE;
|
|
}
|
|
else {
|
|
if ( client->ps.stats[STAT_HEALTH] > 1 ) {
|
|
ent->r.contents = MASK_PLAYERSOLID;
|
|
//ent->r.contents = CONTENTS_BODY;
|
|
}
|
|
}
|
|
|
|
//If we're half-way thru the cycle, teleport the player now
|
|
if ( level.time > TransDat[client->ps.clientNum].beamTime - 4000 &&
|
|
!TransDat[client->ps.clientNum].beamed ) {
|
|
TeleportPlayer( ent, TransDat[client->ps.clientNum].currentCoord.origin,
|
|
TransDat[client->ps.clientNum].currentCoord.angles,
|
|
TP_TRI_TP );
|
|
|
|
TransDat[client->ps.clientNum].beamed = qtrue;
|
|
}
|
|
}
|
|
else {
|
|
//all done, let's reset :)
|
|
if ( TransDat[client->ps.clientNum].beamTime > 0 ) {
|
|
TransDat[client->ps.clientNum].beamTime = 0;
|
|
client->ps.powerups[PW_BEAM_OUT] = 0;
|
|
client->ps.powerups[PW_QUAD] = 0;
|
|
TransDat[client->ps.clientNum].beamed = qfalse;
|
|
|
|
memset( &TransDat[client->ps.clientNum].currentCoord, 0,
|
|
sizeof( TransDat[client->ps.clientNum].currentCoord.origin ) );
|
|
}
|
|
}
|
|
|
|
//TiM : Freeze their movement if they're halfway through a transport cycle
|
|
if ( level.time < TransDat[client->ps.clientNum].beamTime &&
|
|
level.time > TransDat[client->ps.clientNum].beamTime - 4000 )
|
|
{
|
|
vec3_t endPoint;
|
|
trace_t tr;
|
|
VectorSet( endPoint, client->ps.origin[0], client->ps.origin[1], client->ps.origin[2] - 48 );
|
|
//Do a trace down. If we're near ground, just re-enable gravity. Else we we get weird animations O_o
|
|
trap_Trace( &tr, client->ps.origin, NULL, NULL, endPoint, client->ps.clientNum, CONTENTS_SOLID );
|
|
|
|
if ( tr.fraction == 1.0 ) {
|
|
client->ps.gravity = 0;
|
|
client->ps.velocity[2] = 0;
|
|
}
|
|
|
|
client->ps.speed = 0;
|
|
|
|
client->ps.velocity[0] = client->ps.velocity[1] = 0.0;
|
|
}
|
|
|
|
// set up for pmove
|
|
oldEventSequence = client->ps.eventSequence;
|
|
|
|
memset (&pm, 0, sizeof(pm));
|
|
|
|
pm.ps = &client->ps;
|
|
pm.cmd = *ucmd;
|
|
if ( pm.ps->pm_type == PM_DEAD ) {
|
|
pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY;
|
|
}
|
|
else {
|
|
pm.tracemask = MASK_PLAYERSOLID;
|
|
}
|
|
pm.trace = trap_Trace;
|
|
pm.pointcontents = trap_PointContents;
|
|
pm.debugLevel = g_debugMove.integer;
|
|
pm.noFootsteps = ( g_dmflags.integer & DF_NO_FOOTSTEPS ) > 0;
|
|
pm.pModDisintegration = g_pModDisintegration.integer > 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;
|
|
}
|
|
BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue );
|
|
|
|
SendPendingPredictableEvents( &ent->client->ps );
|
|
|
|
// use the snapped origin for linking so it matches client predicted versions
|
|
VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin );
|
|
|
|
VectorCopy (pm.mins, ent->r.mins);
|
|
VectorCopy (pm.maxs, ent->r.maxs);
|
|
|
|
ent->waterlevel = pm.waterlevel;
|
|
ent->watertype = pm.watertype;
|
|
|
|
// execute client events
|
|
ClientEvents( ent, oldEventSequence );
|
|
|
|
if ( pm.useEvent )
|
|
{ //TODO: Use
|
|
TryUse( ent );
|
|
}
|
|
|
|
// link entity now, after any personal teleporters have been used
|
|
trap_LinkEntity (ent);
|
|
// if ( !ent->client->noclip ) {
|
|
G_TouchTriggers( ent );
|
|
// }
|
|
|
|
// NOTE: now copy the exact origin over otherwise clients can be snapped into solid
|
|
VectorCopy( ent->client->ps.origin, ent->r.currentOrigin );
|
|
|
|
//test for solid areas in the AAS file
|
|
BotTestSolid(ent->r.currentOrigin);
|
|
|
|
// touch other objects
|
|
ClientImpacts( ent, &pm );
|
|
|
|
// save results of triggers and client events
|
|
if (ent->client->ps.eventSequence != oldEventSequence) {
|
|
ent->eventTime = level.time;
|
|
}
|
|
|
|
// 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 ( level.time > client->respawnTime ) {
|
|
// forcerespawn is to prevent users from waiting out powerups
|
|
//RPG-X: RedTechie - No forcerespawn
|
|
/*if ( g_forcerespawn.integer > 0 &&
|
|
( level.time - client->respawnTime ) > g_forcerespawn.integer * 1000 ) {
|
|
respawn( ent );
|
|
return;
|
|
}*/
|
|
|
|
// pressing attack or use is the normal respawn method
|
|
if ( ucmd->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) ) {
|
|
respawn( ent );
|
|
return;
|
|
}
|
|
|
|
//in assimilation and elimination, always force respawn
|
|
if ( level.time - client->respawnTime > 1300 && //NOTE: when killed, client->respawnTime = level.time + 1700, so this is 3000 ms
|
|
( g_pModAssimilation.integer || g_pModElimination.integer ) )
|
|
{
|
|
respawn( ent );
|
|
return;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// perform once-a-second actions
|
|
ClientTimerActions( ent, msec );
|
|
|
|
if ( ent->client->teleportTime > 0 && ent->client->teleportTime < level.time )
|
|
{
|
|
if ( ent->client->sess.sessionClass == PC_BORG )
|
|
{
|
|
if ( BG_BorgTransporting( &ent->client->ps ) )
|
|
{
|
|
G_Rematerialize( ent );
|
|
}
|
|
else
|
|
{
|
|
G_GiveHoldable( ent->client, HI_TRANSPORTER );
|
|
ent->client->ps.stats[STAT_USEABLE_PLACED] = 0;
|
|
ent->client->teleportTime = 0;
|
|
}
|
|
}
|
|
else if ( ent->client->sess.sessionClass == PC_DEMO )
|
|
{
|
|
G_GiveHoldable( ent->client, HI_DETPACK );
|
|
ent->client->ps.stats[STAT_USEABLE_PLACED] = 0;
|
|
ent->client->teleportTime = 0;
|
|
}
|
|
else if ( ent->client->sess.sessionClass == PC_ALPHAOMEGA22 )
|
|
{
|
|
G_GiveHoldable( ent->client, HI_TRANSPORTER );
|
|
ent->client->ps.stats[STAT_USEABLE_PLACED] = 0;
|
|
ent->client->teleportTime = 0;
|
|
}
|
|
else if ( ent->client->sess.sessionClass == PC_ADMIN )
|
|
{
|
|
G_GiveHoldable( ent->client, HI_SHIELD );
|
|
ent->client->ps.stats[STAT_USEABLE_PLACED] = 0;
|
|
ent->client->teleportTime = 0;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
==================
|
|
ClientThink
|
|
|
|
A new command has arrived from the client
|
|
==================
|
|
*/
|
|
void ClientThink( int clientNum ) {
|
|
gentity_t *ent;
|
|
|
|
ent = g_entities + clientNum;
|
|
trap_GetUsercmd( clientNum, &ent->client->pers.cmd );
|
|
|
|
// mark the time we got info, so we can display the
|
|
// phone jack if they don't get any for a while
|
|
ent->client->lastCmdTime = level.time;
|
|
|
|
if ( !g_synchronousClients.integer ) {
|
|
ClientThink_real( ent );
|
|
}
|
|
}
|
|
|
|
|
|
void G_RunClient( gentity_t *ent ) {
|
|
if ( !g_synchronousClients.integer ) {
|
|
return;
|
|
}
|
|
ent->client->pers.cmd.serverTime = level.time;
|
|
ClientThink_real( ent );
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
SpectatorClientEndFrame
|
|
|
|
==================
|
|
*/
|
|
void SpectatorClientEndFrame( gentity_t *ent ) {
|
|
gclient_t *cl;
|
|
|
|
// if we are doing a chase cam or a remote view, grab the latest info
|
|
if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) {
|
|
int clientNum;
|
|
|
|
clientNum = ent->client->sess.spectatorClient;
|
|
|
|
// team follow1 and team follow2 go to whatever clients are playing
|
|
if ( clientNum == -1 ) {
|
|
clientNum = level.follow1;
|
|
} else if ( clientNum == -2 ) {
|
|
clientNum = level.follow2;
|
|
}
|
|
if ( clientNum >= 0 ) {
|
|
cl = &level.clients[ clientNum ];
|
|
if ( cl->pers.connected == CON_CONNECTED && cl->sess.sessionTeam != TEAM_SPECTATOR ) {
|
|
ent->client->ps = cl->ps;
|
|
ent->client->ps.pm_flags |= PMF_FOLLOW;
|
|
return;
|
|
} else {
|
|
// drop them to free spectators unless they are dedicated camera followers
|
|
if ( ent->client->sess.spectatorClient >= 0 ) {
|
|
ent->client->sess.spectatorState = SPECTATOR_FREE;
|
|
ClientBegin( ent->client - level.clients, qfalse );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( ent->client->sess.spectatorState == SPECTATOR_SCOREBOARD ) {
|
|
ent->client->ps.pm_flags |= PMF_SCOREBOARD;
|
|
} else {
|
|
ent->client->ps.pm_flags &= ~PMF_SCOREBOARD;
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============
|
|
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;
|
|
clientPersistant_t *pers;
|
|
|
|
if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR || (ent->client->ps.eFlags&EF_ELIMINATED) ) {
|
|
SpectatorClientEndFrame( ent );
|
|
ent->client->noclip = qtrue;
|
|
return;
|
|
}
|
|
|
|
pers = &ent->client->pers;
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
// save network bandwidth
|
|
#if 0
|
|
if ( !g_synchronousClients->integer && ent->client->ps.pm_type == PM_NORMAL ) {
|
|
// FIXME: this must change eventually for non-sync demo recording
|
|
VectorClear( ent->client->ps.viewangles );
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// If the end of unit layout is displayed, don't give
|
|
// the player any normal movement attributes
|
|
//
|
|
if ( level.intermissiontime ) {
|
|
return;
|
|
}
|
|
|
|
// 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);
|
|
|
|
// set the latest infor
|
|
BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue );
|
|
SendPendingPredictableEvents( &ent->client->ps );
|
|
|
|
}
|
|
|
|
|