rpgxef/code/game/g_active.c

3171 lines
83 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 qboolean BG_BorgTransporting( playerState_t *ps );
static int lastTimedMessage; //TiM - Moved here from g_local.h
/*
==============
TryUse
==============
*/
#define USE_DISTANCE 64.0f
/**
* Try and use an entity in the world, directly ahead of us
*/
void TryUse( gentity_t *ent )
{
gentity_t *target; //, *newent;
trace_t trace;
vec3_t src, dest, vf;
clientSession_t *sess;
if(!ent || !ent->client) return;
sess = &ent->client->sess;
VectorCopy( ent->r.currentOrigin, src );
src[2] += ent->client->ps.viewheight * ent->client->pers.pms_height; //TiM - include height offset for tall players
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 && 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 ) != sess->sessionTeam )
{//not on my team
//if ( sess->sessionClass != PC_TECH )
//{//only a tech can use enemy usables
// //FIXME: Play a failure sound
// return;
//}
//TiM - return
return;
}
}
//FIXME: play sound?
target->use( target, ent, ent );
#ifdef G_LUA
if(target->luaUse)
LuaHook_G_EntityUse(target->luaUse, target-g_entities, ent-g_entities, ent-g_entities);
#endif
return;
}
else if ( target && target->use && Q_stricmp("misc_ammo_station", target->classname) == 0 )
{//ammo station
if ( sess->sessionTeam )
{
if ( target->team )
{
if ( atoi( target->team ) != sess->sessionTeam )
{
//FIXME: play sound?
return;
}
}
}
target->use( target, ent, ent );
#ifdef G_LUA
if(target->luaUse)
LuaHook_G_EntityUse(target->luaUse, target-g_entities, ent-g_entities, ent-g_entities);
#endif
return;
}
else if ( (target && 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;
playerState_t *ps;
client = player->client;
ps = &client->ps;
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 ) {
ps->damagePitch = 255;
ps->damageYaw = 255;
client->damage_fromWorld = qfalse;
} else {
vectoangles( client->damage_from, angles );
ps->damagePitch = angles[PITCH]/360.0 * 256;
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 );
ps->damageEvent++;
}
ps->damageCount = client->damage_blood;
if (ps->damageCount > 255)
{
ps->damageCount = 255;
}
ps->damageShieldCount = client->damage_armor;
if (ps->damageShieldCount > 255)
{
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 ||*/ g_classData[ent->client->sess.sessionClass].isMarine/*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 };
playerState_t *ps;
if ( !ent || !ent->client ) {
return;
}
ps = &ent->client->ps;
// dead clients don't activate triggers!
if ( ps->stats[STAT_HEALTH] <= 0 ) {
return;
}
VectorSubtract( ps->origin, range, mins );
VectorAdd( 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( ps->origin, ent->r.mins, mins );
VectorAdd( ps->origin, ent->r.maxs, maxs );
for ( i=0 ; i<num ; i++ ) {
hit = &g_entities[touch[i]];
#ifdef G_LUA
if(hit->luaTouch)
{
LuaHook_G_EntityTouch(hit->luaTouch, hit->s.number, ent->s.number);
}
#endif
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 /*|| (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 )
{
usercmd_t *cmd = &client->pers.cmd;
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 ( cmd->forwardmove ||
cmd->rightmove ||
cmd->upmove ||
(cmd->buttons & BUTTON_ATTACK) ||
(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 };
/**
* \author Ubergames
*/
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 == 1 && 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_LASER] )
{
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_FLASHLIGHT] )
//{
//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())
*
* 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
/**
* The detpack has been shot
*/
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);
}
/**
* Place the detpack
*/
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};
playerState_t *ps = &ent->client->ps;
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 (ps->viewangles, fwd, right, up);
fwd[2] = 0;
VectorMA(ps->origin, detDistance, fwd, end);
trap_Trace (&tr, 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(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;
}
}
/**
* Was a player hit by a detpack.
*/
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;
}
/**
* Addes a player to the detpack hits
*/
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;
}
/**
* Clear the hits for this detpack
*/
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;
}
/**
* Detonate a detpack
*/
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 1022 //254
#define MAX_SHIELD_HALFWIDTH 1023 //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;
static qhandle_t shieldDeactivateSound=0;
/**
* Remove a forcefield
*/
void ShieldRemove(gentity_t *self)
{
self->think = G_FreeEntity;
self->nextthink = level.time + 300;
self->s.eFlags |= EF_ITEMPLACEHOLDER;
// Play raising sound...
G_AddEvent(self, EV_GENERAL_SOUND, shieldDeactivateSound);
return;
}
/**
* The think function of a forcefield
* Does not do much anymore, once, counted down the health of a forcefield and removed
* it when healt got equal or below zero.
*/
void ShieldThink(gentity_t *self)
{
self->s.eFlags &= ~(EF_ITEMPLACEHOLDER | EF_NODRAW);
// self->health -= SHIELD_HEALTH_DEC;
// self->nextthink = level.time + 1000;
self->nextthink = 0;
//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 = qtrue;//RPG-X: - RedTechie use to be qtrue //TiM - made true again. should be okay so long as the health isn't decremented
trap_LinkEntity(self);
// Play raising sound...
//TiM No more
//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;
//TiM - Make the field visible
self->s.eFlags |= EF_ITEMPLACEHOLDER;
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 ( !other || !other->client )
return;
/* 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 (IsAdmin(other) || (rpg_borgAdapt.integer && rpg_borgMoveThroughFields.integer && IsBorg(other))/*other->client->sess.sessionClass == PC_ADMIN*/ )
{
ShieldGoNotSolid(self);
}
//RPG-X:J2J Damage for shields
else
{
if ( (int)(self->s.angles[PITCH]) == 0 )
{
vec3_t dir;
//OPTIMIZE ME: If anyone can figure a quick, non hacky way to get the normal vector
//of the side of the forcefield they touch, PLEASE put it here lol. THis way works, but feels very half-assed lol
//Get the directional vector
VectorSubtract( self->r.currentOrigin, other->r.currentOrigin, dir);
VectorNormalize(dir);
//depending on angle, negate the perpendicular direction (else they zap back sideways)
//and set the other as absolute
if ( self->s.angles[YAW] == 0 ) {
dir[1] = 0.0;
if ( dir[0] < 0 )
dir[0] = -1.0;
else
dir[0] = 1.0;
}
else {
dir[0] = 0.0;
if ( dir[1] < 0 )
dir[1] = -1.0;
else
dir[1] = 1.0;
}
//Invert it otherwise we'd be like, sucked into the field lol
VectorScale(dir, -1, dir);
//Un-normalize it to represent a scalar quantity
VectorScale( dir, 50, dir);
//Copy it straight to our velocity (this will mean the player will literally be thrown back)
VectorCopy( dir, other->client->ps.velocity );
other->client->ps.pm_time = 160; // hold time
other->client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
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_0 );
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);
}
}
}
//}
}
//TiM: make it flicker when touched, and then throw bak the person
self->s.eFlags |= EF_ITEMPLACEHOLDER;
self->nextthink = level.time + 150;
self->think = ShieldThink;
}
}
}
/**
* 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;
//gentity_t *emitter;
// 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
{
ent->s.eFlags |= EF_SHIELD_BOX_Y;
posTraceEnd[1]+=MAX_SHIELD_HALFWIDTH;
negTraceEnd[1]-=MAX_SHIELD_HALFWIDTH;
xaxis = qfalse;
}
else // shield runs along x-axis
{
ent->s.eFlags |= EF_SHIELD_BOX_X;
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);
}
else
{
VectorSet(ent->r.mins, -SHIELD_HALFTHICKNESS, -halfWidth, -(height>>1));
VectorSet(ent->r.maxs, SHIELD_HALFTHICKNESS, halfWidth, height>>1);
}
ent->clipmask = MASK_SHOT;
// scanable forcefield
/*if(rpg_scannableForceField.integer) {
VectorCopy(maxs, ent->s.origin2);
VectorCopy(mins, ent->s.angles2);
trap_LinkEntity(ent);
}*/
// Information for shield rendering.
// xaxis - 1 bit
// height - 0-254 8 bits //10
// posWidth - 0-255 8 bits //10
// negWidth - 0 - 255 8 bits
paramData = (xaxis << 30) | ((height&1023) << 20) | ((posWidth&1023) << 10) | (negWidth&1023); //24 16 8
ent->s.time2 = paramData;
if ( ent->s.otherEntityNum2 == TEAM_RED )
{
ent->team = "1";
}
else if ( ent->s.otherEntityNum2 == TEAM_BLUE )
{
ent->team = "2";
}
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 + 400; //1000
ent->think = ShieldThink;
ent->takedamage = qtrue;//RPG-X: - RedTechie Use to be qtrue //TiM - made true again. should be okay so long as the health isn't decremented
trap_LinkEntity(ent);
ent->s.eFlags |= EF_ITEMPLACEHOLDER;
// Play raising sound...
G_AddEvent(ent, EV_GENERAL_SOUND, shieldActivateSound);
}
return;
}
// After a short delay, create the shield by expanding in all directions.
//void CreateFlatShield(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);
// if ( (int)(ent->s.angles[YAW]) == 0 )
// {
// if ( ent->timestamp )
// end[1] += MAX_SHIELD_HEIGHT;
// else
// end[1] -= MAX_SHIELD_HEIGHT;
// }
// else
// {
// if ( ent->timestamp )
// end[0] += MAX_SHIELD_HEIGHT;
// else
// end[0] -= 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
// if ( (int)(ent->s.angles[YAW]) == 0 ) {
// VectorSet(mins, -SHIELD_HALFTHICKNESS, 0, -SHIELD_HALFTHICKNESS);
// VectorSet(maxs, SHIELD_HALFTHICKNESS, height, SHIELD_HALFTHICKNESS);
// }
// else
// {
// VectorSet(mins, 0, -SHIELD_HALFTHICKNESS, -SHIELD_HALFTHICKNESS);
// VectorSet(maxs, height, SHIELD_HALFTHICKNESS, SHIELD_HALFTHICKNESS);
// }
// 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;
// if ( ent->timestamp )
// {
// posTraceEnd[0]+=MAX_SHIELD_HALFWIDTH;
// negTraceEnd[0]-=MAX_SHIELD_HALFWIDTH;
// }
// else
// {
// posTraceEnd[0]-=MAX_SHIELD_HALFWIDTH;
// negTraceEnd[0]+=MAX_SHIELD_HALFWIDTH;
// }
// xaxis = qfalse;
// }
// else // shield runs along x-axis
// {
// //posTraceEnd[0]+=MAX_SHIELD_HALFWIDTH;
// //negTraceEnd[0]-=MAX_SHIELD_HALFWIDTH;
// if ( ent->timestamp )
// {
// posTraceEnd[1]+=MAX_SHIELD_HALFWIDTH;
// negTraceEnd[1]-=MAX_SHIELD_HALFWIDTH;
// }
// else
// {
// posTraceEnd[1]-=MAX_SHIELD_HALFWIDTH;
// negTraceEnd[1]+=MAX_SHIELD_HALFWIDTH;
// }
//
// xaxis = qtrue;
// }
//
// // trace horizontally to find extend of shield
// // positive trace
// VectorCopy(ent->r.currentOrigin, start);
// if ( (int)(ent->s.angles[YAW]) == 0 )
// {
// if ( ent->timestamp )
// start[1] += (height>>1);
// else
// start[1] -= (height>>1);
// }
// else
// {
// if ( ent->timestamp )
// start[0] += (height>>1);
// else
// start[0] -= (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;
// }
// if ( (int)(ent->s.angles[YAW]) == 0 )
// {
// if ( ent->timestamp )
// ent->r.currentOrigin[1] += (height>>1);
// else
// ent->r.currentOrigin[1] -= (height>>1);
// }
// else
// {
// if ( ent->timestamp )
// ent->r.currentOrigin[0] += (height>>1);
// else
// ent->r.currentOrigin[0] -= (height>>1);
// }
//
// // set entity's mins and maxs to new values, make it solid, and link it
// if (xaxis)
// {
// VectorSet(ent->r.mins, -halfWidth, -(height>>1), -SHIELD_HALFTHICKNESS);
// VectorSet(ent->r.maxs, halfWidth, height, SHIELD_HALFTHICKNESS);
// }
// else
// {
// VectorSet(ent->r.mins, -(height>>1), -halfWidth, -SHIELD_HALFTHICKNESS );
// VectorSet(ent->r.maxs, height>>1, halfWidth, SHIELD_HALFTHICKNESS );
// }
// ent->clipmask = MASK_SHOT;
//
// // Information for shield rendering.
//
//// vertical - 1 bit
//// xaxis - 1 bit
//// height - 0-254 8 bits //10
//// posWidth - 0-255 8 bits //10
//// negWidth - 0 - 255 8 bits
//
// paramData = (ent->timestamp << 32 ) | (1 << 31) |(xaxis << 30) | (height << 20) | (posWidth << 10) | (negWidth); //24 16 8
// 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 + 400; //1000
// ent->think = ShieldThink;
//
// ent->takedamage = qtrue;//RPG-X: - RedTechie Use to be qtrue //TiM - made true again. should be okay so long as the health isn't decremented
// trap_LinkEntity(ent);
//
// ent->s.eFlags |= EF_ITEMPLACEHOLDER;
//
// // Play raising sound...
// G_AddEvent(ent, EV_GENERAL_SOUND, shieldActivateSound);
// }
//
// return;
//}
/**
* Place a forcefield
*/
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};
playerState_t *ps = &playerent->client->ps;
if (shieldAttachSound==0)
{
shieldAttachSound = G_SoundIndex("sound/weapons/detpacklatch.wav");
shieldActivateSound = G_SoundIndex("sound/movers/forcefield/forcefieldon.wav"); //"sound/movers/forceup.wav"
shieldDamageSound = G_SoundIndex("sound/ambience/spark5.wav");
shieldMurderSound = G_SoundIndex("sound/movers/forcefield/forcefieldtouch.wav"); //RPG-X: - RedTechie Added shild ZAP! sound //sound/world/electro.wav
shieldDeactivateSound = G_SoundIndex("sound/movers/forcefield/forcefieldoff.wav");
shieldItem = BG_FindItemForHoldable(HI_SHIELD);
}
// can we place this in front of us?
AngleVectors (ps->viewangles, fwd, NULL, NULL);
fwd[2] = 0;
VectorMA(ps->origin, SHIELD_PLACEDIST, fwd, dest);
trap_Trace (&tr, 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();
if(!shield || !shieldItem) return qfalse;
// 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.origin2[0] = rpg_forceFieldColor.integer;
shield->s.eFlags &= ~EF_NODRAW;
shield->r.svFlags &= ~SVF_NOCLIENT;
// add frequency
/*if(rpg_scannableForceField.integer) {
if(rpg_forceFieldFreq.integer == -1) {
shield->s.apos.trBase[0] = -1;
} else {
shield->s.apos.trBase[0] = rpg_forceFieldFreq.value;
}
}*/
trap_LinkEntity (shield);
// Play placing sound...
G_AddEvent(shield, EV_GENERAL_SOUND, shieldAttachSound);
return qtrue;
}
}
//GARRRGH Screw this! It'll take too long!
//TiM - For a horizontal field, aim it at a vertical plane
//if ( tr.fraction != 1.0 && tr.plane.normal[2] == 0.0f )
//{
// qboolean positive;
// //VectorCopy(tr.endpos, pos);
// // drop to floor
// if ( Q_fabs(tr.plane.normal[0] ) > Q_fabs(tr.plane.normal[1]) )
// {
// //VectorSet( dest, (tr.plane.normal[0]>0 ? pos[0] + 4096 : pos[0] - 4096), pos[1], pos[2] );
// positive = (tr.plane.normal[0]>0);
// }
// else
// {
// //VectorSet( dest, pos[0], (tr.plane.normal[1]>0 ? pos[1] + 4096 : pos[1] - 4096), pos[2] );
// positive = (tr.plane.normal[1]>0);
// }
// //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->s.angles[PITCH] = 90;
// shield->timestamp = positive;
// shield->think = CreateFlatShield;
// 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
/**
* Think function for decoys, decoys are spawnchars in RPG-X
*/
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
}
//TiM : I was just able to spawn 600 copies of me...
//my fps died and my PC started making weird noises
//We'd better instigate a limit to our spawning here...
//Borrowed from the tripMines
/**
* Safety function to limit ammount of decoys spawned at one time and overall.
* Stops spawning if to many are spawned a a time, stop spawning is a limit was hit.
* \author Ubergames - TiM
*/
void flushDecoys( gentity_t *ent ) {
gentity_t *decoy = NULL;
int foundDecoys[MAX_GENTITIES] = {ENTITYNUM_NONE};
int lowestTimeStamp;
int orgCount;
int decoyCount=0;
int removeMe;
int i;
//limit to 10 placed at any one time
//see how many there are now
while ( (decoy = G_Find( decoy, FOFS(classname), "decoy" )) != NULL )
{
if ( decoy->parent != ent )
//if ( decoy->s.clientNum != ent->client->ps.clientNum )
{
continue;
}
foundDecoys[decoyCount++] = decoy->s.clientNum;
}
//G_Printf("Found %i decoys\n", decoyCount++ ) ;
//now remove first ones we find until there are only 9 left
decoy = NULL;
orgCount = decoyCount;
lowestTimeStamp = level.time;
//RPG-X: TiM - Let's limit it to say... 64 decoys per player
while ( decoyCount > 64 ) //9
{
removeMe = -1;
for ( i = 0; i < orgCount; i++ )
{
if ( foundDecoys[i] == ENTITYNUM_NONE )
{
continue;
}
decoy = &g_entities[foundDecoys[i]];
if ( decoy && decoy->timestamp < lowestTimeStamp )
{
removeMe = i;
lowestTimeStamp = decoy->timestamp;
}
}
if ( removeMe != -1 )
{
//remove it... or blow it?
if ( &g_entities[foundDecoys[removeMe]] == NULL )
{
break;
}
else
{
G_FreeEntity( &g_entities[foundDecoys[removeMe]] );
}
foundDecoys[removeMe] = ENTITYNUM_NONE;
decoyCount--;
}
else
{
break;
}
}
}
/**
* entities spawn non solid and through this function,
* they'll become solid once nothing's detected in their boundaries. :)
* \author TiM
*/
void Decoy_CheckForSolidity( gentity_t *ent ) {
int i, num;
int touch[MAX_GENTITIES];
qboolean canGoSolid=qtrue;
gentity_t *hit;
vec3_t mins, maxs;
VectorAdd( ent->s.origin, ent->r.mins, mins );
VectorAdd( ent->s.origin, ent->r.maxs, maxs );
num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
for ( i = 0; i < num; i++ ) {
hit = &g_entities[touch[i]];
if ( hit && hit->client ) {
canGoSolid = qfalse;
break;
}
}
if ( canGoSolid ) {
ent->r.contents = MASK_PLAYERSOLID;
ent->nextthink = 0;
ent->think = 0;
}
else {
ent->nextthink = level.time + 1000;
ent->r.contents = CONTENTS_NONE;
}
}
/**
* Use function for decoy, removes it if activator is an player and admin
*/
void DecoyUse ( gentity_t *self, gentity_t *other, gentity_t *activator ) {
if ( !activator || !IsAdmin( activator ) || !activator->client )
return;
G_FreeEntity( self );
}
/**
* Spawn a char
*/
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};
char userinfo[MAX_INFO_STRING];
int i;
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*/ qtrue )
{
//If we just hit our 129th decoy (...holy crap), reset the counter
if ( level.decoyIndex >= MAX_CLIENTS ) {
level.decoyIndex = 0;
}
//Now check if there is already a decoy with the same eventParm index. If there is, terminate it
{
gentity_t *oldDecoy;
for ( i = 0; i<level.num_entities; i++ ) {
oldDecoy = &g_entities[i];
if ( !Q_stricmp( oldDecoy->classname, "decoy" ) && oldDecoy->s.eventParm == level.decoyIndex ) {
G_FreeEntity( oldDecoy );
break;
}
}
}
//--------------------------- 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);
VectorCopy( ent->client->ps.origin, 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);
VectorCopy( ent->client->ps.viewangles, 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;
decoy->s.number = decoy-g_entities;
//--------------------------- 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).powerups = (ent->s).powerups;
//TiM - attach the rotate flag if we need to
if ( (ent->s).powerups & ( 1<<PW_FLIGHT ) || ent->client->ps.gravity == 0 )
(decoy->s).eFlags |= EF_FULL_ROTATE;
decoy->s.eFlags &= ~(EF_FIRING | EF_ALT_FIRING);
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.pos = ent->s.pos;
//decoy->s.legsAnim = BOTH_STAND1; // Just standing TORSO_STAND
//decoy->s.torsoAnim = BOTH_STAND1;
//TiM: Set it's anim to whatever anims we're playing right now
decoy->s.legsAnim = ent->client->ps.stats[LEGSANIM];
decoy->s.torsoAnim= ent->client->ps.stats[TORSOANIM];
decoy->timestamp = level.time;
//--------------------------- WEAPON ADJUST
//if (decoy->s.weapon==WP_5 /*|| decoy->s.weapon==WP_13*/)
// decoy->s.weapon = WP_1;
// The Phaser and Dreadnought (Arc Welder) weapons are rendered on the
// client side differently, and cannot be used by the decoy
decoy->classname = "decoy";
//flushDecoys( ent );
//TiM-Set up data for transmission to client
decoy->s.eventParm = level.decoyIndex;
decoy->r.contents = CONTENTS_SOLID; //has to start off solid, or CGame won't realise this
VectorSet( decoy->r.mins, DEFAULT_MINS_0, DEFAULT_MINS_1, DEFAULT_MINS_2 );
VectorSet( decoy->r.maxs, DEFAULT_MAXS_0, DEFAULT_MAXS_1, DEFAULT_MAXS_2 );
decoy->nextthink = level.time + FRAMETIME;
decoy->think = Decoy_CheckForSolidity;
decoy->use = DecoyUse;
trap_GetUserinfo( ent->client->ps.clientNum, userinfo, sizeof(userinfo) );
{
char buffer[MAX_TOKEN_CHARS];
char model[64];
char height[9];
char weight[9];
char offset[6];
//TiM - ensure that we encapsulate this data better or else it sometimes
//becomes null
Q_strncpyz( model, Info_ValueForKey( userinfo, "model" ), sizeof( model ) );
Q_strncpyz( height, Info_ValueForKey( userinfo, "height" ), sizeof( height ) );
Q_strncpyz( weight, Info_ValueForKey( userinfo, "weight" ), sizeof( weight ) );
Q_strncpyz( offset, Info_ValueForKey( userinfo, "modelOffset" ), sizeof( offset ) );
Com_sprintf( buffer, sizeof( buffer ), "model\\%s\\height\\%s\\weight\\%s\\moOf\\%s\\c\\%i",
model,
height,
weight,
offset,
ent->client->sess.sessionClass );
trap_SetConfigstring( CS_DECOYS + level.decoyIndex, buffer );
//G_Printf( S_COLOR_BLUE "level.decoyIndex: %i\n", level.decoyIndex );
//G_Printf( S_COLOR_BLUE "%s\n", buffer );
}
level.decoyIndex++;
return qtrue; // SUCCESS
}
/*else
{
return qfalse; // FAILURE: no room
}*/
}
//-------------------------------------------------------------- DECOY ACTIVITIES
void G_Rematerialize( gentity_t *ent )
{
playerState_t *ps = &ent->client->ps;
ent->client->teleportTime = level.time + ( 15 * 1000 );
ps->stats[STAT_USEABLE_PLACED] = 15;
ent->flags &= ~FL_NOTARGET;
ent->takedamage = qtrue;
ent->r.contents = MASK_PLAYERSOLID;
ent->s.eFlags &= ~EF_NODRAW;
ps->eFlags &= ~EF_NODRAW;
TeleportPlayer( ent, ps->origin, ps->viewangles, TP_BORG );
//ps->stats[STAT_USEABLE_PLACED] = 0;
//take it away
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;
playerState_t *ps;
client = ent->client;
ps = &client->ps;
if ( oldEventSequence < ps->eventSequence - MAX_PS_EVENTS ) {
oldEventSequence = ps->eventSequence - MAX_PS_EVENTS;
}
for ( i = oldEventSequence ; i < ps->eventSequence ; i++ ) {
event = 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 ( ps->powerups[PW_REDFLAG] ) {
Team_ReturnFlag(TEAM_RED);
}*/
/*else if ( ps->powerups[PW_BORG_ADAPT] ) {
Team_ReturnFlag(TEAM_BLUE);
}*/
//ps->powerups[PW_BORG_ADAPT] = 0;
//ps->powerups[PW_REDFLAG] = 0;
//------------------------------------------------------- DROP FLAGS
if ( qfalse/*client->sess.sessionClass == PC_BORG*/ )
{
if ( !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;
ps->eFlags |= EF_NODRAW;
ps->stats[STAT_USEABLE_PLACED] = 2;
client->teleportTime = level.time + 10000;
tent = G_TempEntity( 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( ps->origin, origin, angles );
//if(TransDat[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[ps->clientNum].beamTime == 0 &&
VectorCompare( vec3_origin, TransDat[ps->clientNum].storedCoord[TPT_PORTABLE].origin ) &&
VectorCompare( vec3_origin, TransDat[ps->clientNum].storedCoord[TPT_PORTABLE].angles ) )
{
VectorCopy( ps->origin, TransDat[ps->clientNum].storedCoord[TPT_PORTABLE].origin );
VectorCopy( ps->viewangles, TransDat[ps->clientNum].storedCoord[TPT_PORTABLE].angles );
//VectorCopy(ps->origin, TransDat[ps->clientNum].pTransCoord);
//VectorCopy(ps->viewangles, TransDat[ps->clientNum].pTransCoordRot);
//TransDat[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.") );
ps->stats[STAT_HOLDABLE_ITEM] = BG_FindItemForHoldable( HI_TRANSPORTER ) - bg_itemlist;
ps->stats[STAT_USEABLE_PLACED] = 2; // = 1
break;
}
//TeleportPlayer( ent, TransDat[ps->clientNum].pTransCoord, TransDat[ps->clientNum].pTransCoordRot, TP_NORMAL );
if ( TransDat[ps->clientNum].beamTime == 0 && level.time > ps->powerups[PW_QUAD] ) {
G_InitTransport( ps->clientNum, TransDat[ps->clientNum].storedCoord[TPT_PORTABLE].origin,
TransDat[ps->clientNum].storedCoord[TPT_PORTABLE].angles );
memset( &TransDat[ps->clientNum].storedCoord[TPT_PORTABLE], 0, sizeof( TransDat[ps->clientNum].storedCoord[TPT_PORTABLE]) );
}
else {
trap_SendServerCommand( ent-g_entities, va("chat \"Unable to comply. Already within transport cycle.\"", Q_COLOR_ESCAPE));
}
ps->stats[STAT_USEABLE_PLACED] = 0;
if (g_classData[client->sess.sessionClass].isMarine/* client->sess.sessionClass == PC_ALPHAOMEGA22*/ )
{
client->teleportTime = level.time + ( 3 * 1000 ); // 15 * 1000
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 += ps->stats[STAT_MAX_HEALTH]*0.25;
if (ent->health < ps->stats[STAT_MAX_HEALTH])
{ // If that doesn't bring us up to 100, make it go up to 100.
ent->health = ps->stats[STAT_MAX_HEALTH];
}
else if (ent->health > ps->stats[STAT_MAX_HEALTH]*2)
{ // Otherwise, 25 is all you get. Just make sure we don't go above 200.
ent->health = ps->stats[STAT_MAX_HEALTH]*2;
}
break;
case EV_USE_ITEM3: // detpack
// if we haven't placed it yet, place it
if (0 == ps->stats[STAT_USEABLE_PLACED])
{
if ( PlaceDetpack(ent) )
{
ps->stats[STAT_USEABLE_PLACED] = 1;
trap_SendServerCommand( ent-g_entities, "cp \"CHARGE PLACED\"" );
}
else
{//couldn't place it
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
//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.
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
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 ( client->sess.sessionClass == PC_ADMIN )
// {
// client->teleportTime = level.time + ( 30 * 1000 );
// ps->stats[STAT_USEABLE_PLACED] = 30;
//}
}
break;
case EV_USE_ITEM5: // decoy
if ( !PlaceDecoy(ent) )
{//couldn't place it
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);
/*
=============
ThrowWeapon
RPG-X
Marcin 03/12/2008
=============
*/
/**
* Throw a weapon away.
* \author Ubergames - Marcin
* \date 03/12/2008
*/
void ThrowWeapon( gentity_t *ent, char *txt )
{
gclient_t *client;
usercmd_t *ucmd;
gitem_t *item;
gentity_t *drop;
byte i;
playerState_t *ps;
client = ent->client;
ucmd = &ent->client->pers.cmd;
ps = &client->ps;
if ( rpg_allowWeaponDrop.integer == 0) {
return;
}
if ( numTotalDropped >= MAX_DROPPED ) {
WARNING(("RPG-X Warning: maximum of dropped items of %i reached.\n", MAX_DROPPED));
return;
}
if ( ps->weapon == WP_1 || ( ucmd->buttons & BUTTON_ATTACK )) {
return;
}
numTotalDropped++;
item = BG_FindItemForWeapon( ps->weapon );
// admins don't lose weapon when thrown
if ( IsAdmin( ent ) == qfalse ) {
ps->ammo[ ps->weapon ] -= 1;
if (ps->ammo[ ps->weapon ] <= 0) {
ps->stats[STAT_WEAPONS] &= ~( 1 << ps->weapon );
ps->weapon = WP_1;
for ( i = WP_NUM_WEAPONS - 1 ; i > 0 ; i-- ) {
if ( ps->stats[STAT_WEAPONS] & ( 1 << i ) ) {
ps->weapon = i;
break;
}
}
}
}
drop = DropWeapon( ent, item, 0, FL_DROPPED_ITEM | FL_THROWN_ITEM, txt );
drop->parent = ent;
drop->count = 1;
}
/*
==============
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;
}
}
void ClientCamThink(gentity_t *client) {
if(!client->client->cam) return;
G_SetOrigin(client, client->client->cam->s.origin);
SetClientViewAngle(client, client->client->cam->s.angles);
trap_LinkEntity(client);
}
/*
==============
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;
playerState_t *ps;
client = ent->client;
ps = &client->ps;
// 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 = &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 - 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;
}
if (ent->flags & FL_CCAM) {
ClientCamThink( ent );
return;
}
// Don't move while under intro sequence.
/*if (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 /*|| (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 ) ) {
if ( ps->stats[STAT_HEALTH] > 1 )
ent->r.contents = CONTENTS_BODY;
else
ent->r.contents = CONTENTS_CORPSE;
}
//else {
// ent->r.contents = CONTENTS_NONE;
//}
}
// clear the rewards if time
/*if ( level.time > client->rewardTime ) {
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 ) {
ps->pm_type = PM_NOCLIP;
} else if ( ps->stats[STAT_HEALTH] == 1 ) {
ps->pm_type = PM_DEAD;
} else {
ps->pm_type = PM_NORMAL;
}
}else{
if ( client->noclip ) {
ps->pm_type = PM_NOCLIP;
} else if ( ps->stats[STAT_HEALTH] <= 0 ) {
ps->pm_type = PM_DEAD;
} else {
ps->pm_type = PM_NORMAL;
}
}
//RPG-X: J2J & Phenix - For the gravity ent
if(client->SpecialGrav != qtrue)
{
ps->gravity = g_gravity.value;
}
// set speed
ps->speed = g_speed.value;
if ( ps->powerups[PW_HASTE] )
{
ps->speed *= 1.5;
}
else if (ps->powerups[PW_FLIGHT] )
{//flying around
ps->speed *= 1.3;
}
else if ( ps->stats[STAT_HEALTH] <= 20 ) {
ps->speed *= 0.55;
}
if (( ps->powerups[PW_EVOSUIT] ) && ( ps->gravity == 0 ))
{//Evosuit time.. RPG-X | Phenix | 8/8/2004
ps->speed *= 1.3;
}
//RPG-X: Redtechie - n00bie stay.....good boy!
if ( g_classData[client->sess.sessionClass].isn00b /*client->sess.sessionClass == PC_N00B*/ ){
ps->speed = 0;
}
/*if ( client->sess.sessionClass == PC_HEAVY )
{
ps->speed *= 0.75;
}
if ( client->sess.sessionClass == PC_BORG )
{
if ( BG_BorgTransporting( &client->ps ) )
{
ps->speed *= 1.5;
}
else if ( !BG_BorgTransporting( &client->ps ) )
{
ps->speed *= 0.75;
}
}*/
//TiM : SP Style Transporter. :)
//first check to see if we should be beaming
if ( level.time < TransDat[ps->clientNum].beamTime ) {
//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[ps->clientNum].beamTime - 6000) &&
( level.time < TransDat[ps->clientNum].beamTime - 2000 ) ) {
if ( ps->stats[STAT_HEALTH] > 1 ) {
ent->r.contents = CONTENTS_NONE;
}
//ent->r.contents = CONTENTS_CORPSE;
}
else {
if ( 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[ps->clientNum].beamTime - 4000 &&
!TransDat[ps->clientNum].beamed ) {
TeleportPlayer( ent, TransDat[ps->clientNum].currentCoord.origin,
TransDat[ps->clientNum].currentCoord.angles,
TP_TRI_TP );
TransDat[ps->clientNum].beamed = qtrue;
}
}
else {
//all done, let's reset :)
if ( TransDat[ps->clientNum].beamTime > 0 ) {
TransDat[ps->clientNum].beamTime = 0;
ps->powerups[PW_BEAM_OUT] = 0;
ps->powerups[PW_QUAD] = 0;
TransDat[ps->clientNum].beamed = qfalse;
memset( &TransDat[ps->clientNum].currentCoord, 0,
sizeof( TransDat[ps->clientNum].currentCoord.origin ) );
if(g_entities[ps->clientNum].flags & FL_CLAMPED) {
//reset everything if player was beamed by trigger_transporter
g_entities[ps->clientNum].flags ^= FL_CLAMPED;
}
}
}
//TiM : Freeze their movement if they're halfway through a transport cycle
if ( level.time < TransDat[ps->clientNum].beamTime &&
level.time > TransDat[ps->clientNum].beamTime - 4000 )
{
vec3_t endPoint;
trace_t tr;
VectorSet( endPoint, ps->origin[0], ps->origin[1], 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, ps->origin, NULL, NULL, endPoint, ps->clientNum, CONTENTS_SOLID );
if ( tr.fraction == 1.0 ) {
ps->gravity = 0;
ps->velocity[2] = 0;
}
ps->speed = 0;
ps->velocity[0] = ps->velocity[1] = 0.0;
}
// set up for pmove
oldEventSequence = 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 = qfalse;
//pm.admin = IsAdmin(ent); // we use this way now the old way didn't work for adminlogin
// y call a function though???
pm.admin = g_classData[client->sess.sessionClass].isAdmin || client->LoggedAsAdmin;
//pm.admin = g_classData[client->sess.sessionClass].isAdmin;
pm.medic = g_classData[client->sess.sessionClass].isMedical;
pm.borg = g_classData[client->sess.sessionClass].isBorg;
VectorCopy( ps->origin, oldOrigin );
// perform a pmove
Pmove (&pm);
// save results of pmove
if ( ps->eventSequence != oldEventSequence ) {
ent->eventTime = level.time;
}
BG_PlayerStateToEntityState( ps, &ent->s, qtrue );
SendPendingPredictableEvents( 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 ( !client->noclip ) {
G_TouchTriggers( ent );
// }
// NOTE: now copy the exact origin over otherwise clients can be snapped into solid
VectorCopy( 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 (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 ( client->teleportTime > 0 && client->teleportTime < level.time )
{
//if ( client->sess.sessionClass == PC_BORG )
//{
// if ( BG_BorgTransporting( ps ) )
// {
// G_Rematerialize( ent );
// }
// else
// {
// G_GiveHoldable( client, HI_TRANSPORTER );
// ps->stats[STAT_USEABLE_PLACED] = 0;
// client->teleportTime = 0;
// }
//}
//else if ( client->sess.sessionClass == PC_DEMO )
//{
// G_GiveHoldable( client, HI_DETPACK );
// ps->stats[STAT_USEABLE_PLACED] = 0;
// client->teleportTime = 0;
//}
/*else */if ( g_classData[client->sess.sessionClass].isMarine /*client->sess.sessionClass == PC_ALPHAOMEGA22*/ )
{
G_GiveHoldable( client, HI_TRANSPORTER );
ps->stats[STAT_USEABLE_PLACED] = 0;
client->teleportTime = 0;
}
else if ( g_classData[client->sess.sessionClass].isAdmin /*client->sess.sessionClass == PC_ADMIN*/ )
{
G_GiveHoldable( client, HI_SHIELD );
ps->stats[STAT_USEABLE_PLACED] = 0;
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;
clientSession_t *sess = &ent->client->sess;
playerState_t *ps = &ent->client->ps;
// if we are doing a chase cam or a remote view, grab the latest info
if ( sess->spectatorState == SPECTATOR_FOLLOW ) {
int clientNum;
clientNum = 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;
ps->pm_flags |= PMF_FOLLOW;
return;
} else {
// drop them to free spectators unless they are dedicated camera followers
if ( sess->spectatorClient >= 0 ) {
sess->spectatorState = SPECTATOR_FREE;
ClientBegin( ent->client - level.clients, qfalse, qfalse, qfalse );
}
}
}
}
if ( sess->spectatorState == SPECTATOR_SCOREBOARD ) {
ps->pm_flags |= PMF_SCOREBOARD;
} else {
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;
playerState_t *ps = &ent->client->ps;
if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR /*|| (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 ( ps->powerups[ i ] < level.time ) {
ps->powerups[ i ] = 0;
}
}
// save network bandwidth
#if 0
if ( !g_synchronousClients->integer && ps->pm_type == PM_NORMAL ) {
// FIXME: this must change eventually for non-sync demo recording
VectorClear( 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;
}
ps->stats[STAT_HEALTH] = ent->health; // FIXME: get rid of ent->health...
G_SetClientSound (ent);
// set the latest infor
BG_PlayerStateToEntityState( ps, &ent->s, qtrue );
SendPendingPredictableEvents( ps );
}