rpgxef/code/game/g_active.c.orig
Walter Julius Hennecke a39565b783 Integrated RPG-X2 rpgxEF edition into the rpgxEF repo
... 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)
2012-08-04 12:54:37 +02:00

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 );
}