From a918a505b058d614e0a274bffdc5613d848ddb2a Mon Sep 17 00:00:00 2001 From: Walter Julius Hennecke Date: Wed, 13 Aug 2014 23:30:47 +0200 Subject: [PATCH] Fixed warning in g_client.c --- code/game/g_client.c | 7016 +++++++++++++++++++++--------------------- 1 file changed, 3508 insertions(+), 3508 deletions(-) diff --git a/code/game/g_client.c b/code/game/g_client.c index 4377983..a0ecc53 100644 --- a/code/game/g_client.c +++ b/code/game/g_client.c @@ -1,3508 +1,3508 @@ -// Copyright (C) 1999-2000 Id Software, Inc. -// -#include "g_client.h" -#include "g_local.h" -#include "g_groups.h" -#include "g_main.h" -#include "g_cmds.h" -#include "g_main.h" -#include "g_spawn.h" -#include "g_items.h" -#include "g_lua.h" -#include "g_logger.h" - -reconData_t g_reconData[MAX_RECON_NAMES]; //!< recon data for a limited ammount of clients -int32_t g_reconNum; - -// g_client.c -- client functions that don't happen every frame - -void G_Client_StoreClientInitialStatus( gentity_t *ent ); - -//! players mins -static vec3_t playerMins = {-12, -12, -24}; //RPG-X : TiM - {-15, -15, -24} -//! players maxs -static vec3_t playerMaxs = {12, 12, 32}; // {15, 15, 32} - -clInitStatus_t clientInitialStatus[MAX_CLIENTS]; - -//TiM: For easier transport setup -/** -* Function that makes transport setup easier -* \author Ubergames - TiM -*/ -void G_InitTransport( int32_t clientNum, vec3_t origin, vec3_t angles ) { - gentity_t* tent = NULL; - - TransDat[clientNum].beamTime = level.time + 8000; - g_entities[clientNum].client->ps.powerups[PW_BEAM_OUT] = level.time + 8000; - - //Transfer stored data to active beamer - VectorCopy( origin, TransDat[clientNum].currentCoord.origin ); - VectorCopy( angles, TransDat[clientNum].currentCoord.angles ); - - tent = G_TempEntity( g_entities[clientNum].client->ps.origin, EV_PLAYER_TRANSPORT_OUT ); - tent->s.clientNum = clientNum; -} - -/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) INITIAL ------DESCRIPTION----- -potential spawning position for deathmatch games. - ------SPAWNFLAGS----- -1: INITIAL - Preferred spawn for the first spawn of a client when entering a match. - ------KEYS----- -"target" - entities with matching targetname will be fired if someone spawns here. -"nobots" - if 1 will prevent bots from using this spot. -"nohumans" - if 1 will prevent non-bots from using this spot. -*/ -/** -* Spawn function for deathmatch spawnpoint -*/ -void SP_info_player_deathmatch( gentity_t *ent ) { - int32_t i = 0; - - ent->type = ENT_INFO_PLAYER_START; - - if(strcmp(ent->classname, "info_player_deathmatch") != 0) { - ent->classname = "info_player_deathmatch"; - } - - G_SpawnInt( "nobots", "0", &i); - if ( i != 0 ) { - ent->flags |= FL_NO_BOTS; - } - G_SpawnInt( "nohumans", "0", &i ); - if ( i != 0 ) { - ent->flags |= FL_NO_HUMANS; - } - - trap_LinkEntity(ent); -} - -/*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32) ------DESCRIPTION----- -The intermission will be viewed from this point. -It is also used to spawn spectators. -Target an info_notnull or similar for the view direction. - ------SPAWNFLAGS----- -none - ------KEYS----- -none -*/ -/** -* Spawn function for intermission entity. -*/ -void SP_info_player_intermission( gentity_t *ent ) { - ent->type = ENT_INFO_PLAYER_INTERMISSION; -} - -/** - * Determine whether spot would telefrag. - * - * \param spot Spot to check. - * \return Whether this spot would telefrag. - */ -static qboolean G_Client_SpotWouldTelefrag( gentity_t *spot ) { - int32_t i = 0; - int32_t num = 0; - int touch[MAX_GENTITIES]; - gentity_t* hit = NULL; - vec3_t mins; - vec3_t maxs; - - memset(touch, 0, sizeof(touch)); - - VectorAdd( spot->s.origin, playerMins, mins ); - VectorAdd( spot->s.origin, playerMaxs, maxs ); - num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); - - for ( ; iclient && hit->client->ps.stats[STAT_HEALTH] > 0 ) { - return qtrue; - } - if (hit && hit->s.eType == ET_USEABLE && hit->s.modelindex == HI_SHIELD) { //hit a portable force field - return qtrue; - } - - - } - - return qfalse; -} - -/* -================ -SelectNearestDeathmatchSpawnPoint - -Find the spot that we DON'T want to use -================ -*/ -#define MAX_SPAWN_POINTS 256 -/** -* Find the spot that we DON'T want to use -*/ -static gentity_t* SelectNearestDeathmatchSpawnPoint( vec3_t from ) { - gentity_t* spot = NULL; - vec3_t delta; - double dist = 0.0; - double nearestDist = 999999.0; - gentity_t* nearestSpot = NULL; - - while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { - - VectorSubtract( spot->s.origin, from, delta ); - dist = VectorLength( delta ); - if ( dist < nearestDist ) { - nearestDist = dist; - nearestSpot = spot; - } - } - - return nearestSpot; -} - - -/* -================ -SelectRandomDeathmatchSpawnPoint - -go to a random point that doesn't telefrag -================ -*/ -#define MAX_SPAWN_POINTS 256 -/** -* go to a random point that doesn't telefrag -*/ -static gentity_t* SelectRandomDeathmatchSpawnPoint( void ) { - gentity_t* spot = NULL; - int32_t count = 0; - int32_t selection = 0; - gentity_t* spots[MAX_SPAWN_POINTS]; - - memset(spots, 0, sizeof(spots)); - - while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { - if ( G_Client_SpotWouldTelefrag( spot ) ) { - continue; - } - spots[ count ] = spot; - count++; - } - - if ( count == 0 ) { // no spots that won't telefrag - return G_Find( NULL, FOFS(classname), "info_player_deathmatch"); - } - - selection = rand() % count; - return spots[ selection ]; -} - - -gentity_t* G_Client_SelectSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) { - gentity_t* spot = NULL; - gentity_t* nearestSpot = SelectNearestDeathmatchSpawnPoint( avoidPoint ); - - spot = SelectRandomDeathmatchSpawnPoint ( ); - if ( spot == nearestSpot ) { - // roll again if it would be real close to point of death - spot = SelectRandomDeathmatchSpawnPoint ( ); - if ( spot == nearestSpot ) { - // last try - spot = SelectRandomDeathmatchSpawnPoint ( ); - } - } - - // find a single player start spot - if (spot == NULL) { - G_Error( "Couldn't find a spawn point" ); - return spot; - } - - VectorCopy (spot->s.origin, origin); - origin[2] += 9; - VectorCopy (spot->s.angles, angles); - - return spot; -} - -/* -=========== -SelectInitialSpawnPoint - -Try to find a spawn point marked 'initial', otherwise -use normal spawn selection. -============ -*/ -/** -* Try to find a spawn point marked 'initial', otherwise -* use normal spawn selection. -*/ -static gentity_t* SelectInitialSpawnPoint( vec3_t origin, vec3_t angles ) { - gentity_t* spot = NULL; - - spot = NULL; - while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { - if ( spot->spawnflags & 1 ) { - break; - } - } - - if ( spot == NULL || G_Client_SpotWouldTelefrag( spot ) ) { - return G_Client_SelectSpawnPoint( vec3_origin, origin, angles ); - } - - VectorCopy (spot->s.origin, origin); - origin[2] += 9; - VectorCopy (spot->s.angles, angles); - - return spot; -} - -/* -=========== -SelectSpectatorSpawnPoint - -============ -*/ -static gentity_t* SelectSpectatorSpawnPoint( vec3_t origin, vec3_t angles ) { - FindIntermissionPoint(); - - VectorCopy( level.intermission_origin, origin ); - VectorCopy( level.intermission_angle, angles ); - - return NULL; -} - -/* -======================================================================= - -BODYQUE - -======================================================================= -*/ - -static int32_t bodyFadeSound=0; - - -/* -=============== -G_Client_InitBodyQue -=============== -*/ -void G_Client_InitBodyQue (void) { - int32_t i = 0; - gentity_t* ent = NULL; - - level.bodyQueIndex = 0; - for ( ; iclassname = "bodyque"; - ent->neverFree = qtrue; - level.bodyQue[i] = ent; - } - - if (bodyFadeSound == 0) - { // Initialize this sound. - bodyFadeSound = G_SoundIndex("sound/enemies/borg/walkthroughfield.wav"); - } -} - -/* -============= -BodyRezOut - -After sitting around for five seconds, fade out. -============= -*/ -/** -* After sitting around for five seconds, fade out. -*/ -static void BodyRezOut( gentity_t *ent ) { - if ( level.time - ent->timestamp >= 7500 ) { - // the body ques are never actually freed, they are just unlinked - trap_UnlinkEntity( ent ); - ent->physicsObject = qfalse; - return; - } - - ent->nextthink = level.time + 2500; - ent->s.time = level.time + 2500; - - G_AddEvent(ent, EV_GENERAL_SOUND, bodyFadeSound); -} - -/* -============= -CopyToBodyQue - -A player is respawning, so make an entity that looks -just like the existing corpse to leave behind. -============= -*/ -/** -* A player is respawning, so make an entity that looks -* just like the existing corpse to leave behind. -*/ -static void CopyToBodyQue( gentity_t *ent ) { - gentity_t* body = NULL; - int32_t contents = 0; - entityState_t* eState = NULL; - - trap_UnlinkEntity (ent); - - // if client is in a nodrop area, don't leave the body - contents = trap_PointContents( ent->s.origin, -1 ); - if ( (contents & CONTENTS_NODROP) != 0 ) { - ent->s.eFlags &= ~EF_NODRAW; // Just in case we died from a bottomless pit, reset EF_NODRAW - return; - } - - // grab a body que and cycle to the next one - body = level.bodyQue[ level.bodyQueIndex ]; - level.bodyQueIndex = (level.bodyQueIndex + 1) % BODY_QUEUE_SIZE; - - trap_UnlinkEntity (body); - - eState = &ent->s; - eState->eFlags = EF_DEAD; // clear EF_TALK, etc - eState->powerups = 0; // clear powerups - eState->loopSound = 0; // clear lava burning - eState->number = body - g_entities; - body->timestamp = level.time; - body->physicsObject = qtrue; - body->physicsBounce = 0; // don't bounce - if ( eState->groundEntityNum == ENTITYNUM_NONE ) { - eState->pos.trType = TR_GRAVITY; - eState->pos.trTime = level.time; - VectorCopy( ent->client->ps.velocity, eState->pos.trDelta ); - } else { - eState->pos.trType = TR_STATIONARY; - } - eState->event = 0; - - // change the animation to the last-frame only, so the sequence - // doesn't repeat anew for the body - switch ( eState->legsAnim & ~ANIM_TOGGLEBIT ) { - case BOTH_DEATH1: - case BOTH_DEAD1: - eState->torsoAnim = eState->legsAnim = BOTH_DEAD1; - break; - case BOTH_DEATH2: - case BOTH_DEAD2: - eState->torsoAnim = eState->legsAnim = BOTH_DEAD2; - break; - default: - eState->torsoAnim = eState->legsAnim = BOTH_DEAD1; //DEAD3 - break; - } - - body->r.svFlags = ent->r.svFlags; - VectorCopy (ent->r.mins, body->r.mins); - VectorCopy (ent->r.maxs, body->r.maxs); - VectorCopy (ent->r.absmin, body->r.absmin); - VectorCopy (ent->r.absmax, body->r.absmax); - - body->clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP; - body->r.contents = CONTENTS_CORPSE; - body->r.ownerNum = ent->r.ownerNum; - - body->nextthink = level.time + 5000; - body->think = BodyRezOut; - - body->die = body_die; - - // if there shouldn't be a body, don't show one. - if (ent->client != NULL && - ((level.time - ent->client->ps.powerups[PW_DISINTEGRATE]) < 10000 || - (level.time - ent->client->ps.powerups[PW_EXPLODE]) < 10000)) - { - eState->eFlags |= EF_NODRAW; - } - - // don't take more damage if already gibbed - //RPG-X: RedTechie - Check for medicrevive - if(rpg_medicsrevive.integer == 0){ - if ( ent->health <= GIB_HEALTH ) { - body->takedamage = qfalse; - } else { - body->takedamage = qtrue; - } - } else { - body->takedamage = qfalse; - } - - VectorCopy ( eState->pos.trBase, body->r.currentOrigin ); - trap_LinkEntity (body); -} - -//====================================================================== - - -/* -================== -G_Client_SetViewAngle - -================== -*/ -void G_Client_SetViewAngle( gentity_t *ent, vec3_t angle ) { - int i; - - // set the delta angle - for (i=0 ; i<3 ; i++) { - int cmdAngle; - - cmdAngle = ANGLE2SHORT(angle[i]); - ent->client->ps.delta_angles[i] = cmdAngle - ent->client->pers.cmd.angles[i]; - } - VectorCopy( angle, ent->s.angles ); - VectorCopy (ent->s.angles, ent->client->ps.viewangles); -} - -/* -================ -G_Client_Respawn -================ -*/ -void G_Client_Respawn( gentity_t *ent ) { - qboolean borg = qfalse; - gentity_t* tent = NULL; - playerState_t* ps = NULL; - - CopyToBodyQue (ent); - - G_Client_Spawn(ent, 0, qfalse);//RPG-X: RedTechie - Modifyed - - ps = &ent->client->ps; - - // add a teleportation effect - if ( borg ) { - tent = G_TempEntity( ps->origin, EV_BORG_TELEPORT ); - } else { - tent = G_TempEntity( ps->origin, EV_PLAYER_TRANSPORT_IN ); - ps->powerups[PW_QUAD] = level.time + 4000; - } - - tent->s.clientNum = ent->s.clientNum; -} - -/** - * Get number of clients in team. - * - * \param ignoreClientNum Client to ignore. - * \param team Team. - * \reutrn Number of clients in team. - */ -static team_t G_Client_TeamCount( int ignoreClientNum, int team ) { - int i; - int count = 0; - - for ( i = 0 ; i < level.maxclients ; i++ ) { - if ( i == ignoreClientNum ) { - continue; - } - if ( level.clients[i].pers.connected == CON_DISCONNECTED ) { - continue; - } - if ( level.clients[i].sess.sessionTeam == team ) { - count++; - } - } - - return (team_t)count; -} - -team_t G_Client_PickTeam( int ignoreClientNum ) { - int counts[TEAM_NUM_TEAMS]; - - counts[TEAM_BLUE] = G_Client_TeamCount( ignoreClientNum, TEAM_BLUE ); - counts[TEAM_RED] = G_Client_TeamCount( ignoreClientNum, TEAM_RED ); - - if ( counts[TEAM_BLUE] > counts[TEAM_RED] ) { - return TEAM_RED; - } - if ( counts[TEAM_RED] > counts[TEAM_BLUE] ) { - return TEAM_BLUE; - } - // equal team count, so join the team with the lowest score - if ( level.teamScores[TEAM_BLUE] > level.teamScores[TEAM_RED] ) { - return TEAM_RED; - } - if ( level.teamScores[TEAM_BLUE] < level.teamScores[TEAM_RED] ) { - return TEAM_BLUE; - } - return (team_t)irandom( TEAM_RED, TEAM_BLUE ); -} - -/* -=========== -ForceClientSkin - -Forces a client's skin (for teamplay) -HEAVILY modified for the RPG-X -Player Model system :P -=========== -*/ -/** -* Forces a client's skin (for teamplay) -* HEAVILY modified for the RPG-X -* Player Model system -*/ -static void ForceClientSkin(char* model, const char* skin ) { - char* p = NULL; - char* q = NULL; - - //we expect model to equal 'char/model/skin' - - p = strchr(model, '/'); - - //if no slashes at all - if ( p == NULL || p[0] == 0 || p[1] == 0 ) { - //input everything - strncat(model, "/", MAX_QPATH); - strncat(model, "main", MAX_QPATH); - strncat(model, "/", MAX_QPATH); - strncat(model, skin, MAX_QPATH); - } else { //ie we got a slash (which should be the first of two - p++; - q = strchr(p, '/'); //okay, we should get another one if one was already found - if (q == NULL || q[0] == 0 || q[1] == 0 ) - { //no slashes were found?? >.< - //okay, let's assume they specified the .model file, no skin - //so just add the skin to the end :P - strncat(model, "/", MAX_QPATH); - strncat(model, skin, MAX_QPATH); - } else { - q++; - *q= '\0'; - strncat(model, skin, MAX_QPATH); - } - } -} - -/* -=========== -ClientCheckName -============ -*/ -static void ClientCleanName( const char* in, char* out, int outSize ) { - int32_t len = 0; - int32_t colorlessLen = 0; - char ch = 0; - char* p = NULL; - int32_t spaces = 0; - - //save room for trailing null byte - outSize--; - - p = out; - *p = 0; - spaces = 0; - - while( 1 ) { - ch = *in++; - if( ch == 0 ) { - break; - } - - // don't allow leading spaces - if( !*p && ch == ' ' ) { - continue; - } - - // check colors - if( ch == Q_COLOR_ESCAPE ) { - // solo trailing carat is not a color prefix - if( !*in ) { - break; - } - - // don't allow black in a name, period - if( ColorIndex(*in) == 0 ) { - in++; - continue; - } - - // make sure room in dest for both chars - if( len > outSize - 2 ) { - break; - } - - *out++ = ch; - *out++ = *in++; - len += 2; - continue; - } - - // don't allow too many consecutive spaces - if( ch == ' ' ) { - spaces++; - if( spaces > 3 ) { - continue; - } - } - else { - spaces = 0; - } - - if( len > outSize - 1 ) { - break; - } - - *out++ = ch; - colorlessLen++; - len++; - } - *out = 0; - - // don't allow empty names - if( *p == 0 || colorlessLen == 0 ) - { - Q_strncpyz( p, "RedShirt", outSize ); - } -} - -/* -=========== -legalSkin - -Compare a list of races with an incoming race name. -Used to decide if in a CTF game where a race is specified for a given team if a skin is actually already legal. -=========== -*/ -/** -* Compare a list of races with an incoming race name. -* Used to decide if in a CTF game where a race is specified for a given team if a skin is actually already legal. -*/ -static qboolean legalSkin(const char*race_list, const char* race) -{ - char current_race_name[125]; - const char *s = race_list; - const char *max_place = race_list + strlen(race_list); - const char *marker = NULL; - - memset(current_race_name, 0, sizeof(current_race_name)); - // look through the list till it's empty - while (s < max_place) - { - marker = s; - // figure out from where we are where the next ',' or 0 is - while (*s != ',' && *s != 0) - { - s++; - } - - // copy just that name - strncpy(current_race_name, marker, (s-marker)+1); - - // avoid the comma or increment us past the end of the string so we fail the main while loop - s++; - - // compare and see if this race is the same as the one we want - if (Q_stricmp(current_race_name, race) == 0) - { - return qtrue; - } - } - return qfalse; -} - - -/* -=========== -randomSkin - -given a race name, go find all the skins that use it, and randomly select one -=========== -*/ -/** -* given a race name, go find all the skins that use it, and randomly select one -*/ -static void randomSkin(const char* race, char* model, int32_t current_team, int32_t clientNum) -{ - char** skinsForRace = NULL; - int32_t howManySkins = 0; - int32_t i = 0; - int32_t x = 0; - int32_t temp = 0; - int32_t skin_count_check = 0; - char** skinNamesAlreadyUsed = NULL; - int32_t current_skin_count = 0; - gentity_t* ent = NULL; - char* userinfo = NULL; - char temp_model[MAX_QPATH]; - - memset(temp_model, 0, sizeof(temp_model)); - - skinsForRace = malloc(MAX_SKINS_FOR_RACE * 128 * sizeof(char)); - if(skinsForRace == NULL) { - G_Error("Was unable to allocate %i bytes.\n", MAX_SKINS_FOR_RACE * 128 * sizeof(char)); - return; - } - skinNamesAlreadyUsed = malloc(16 * 128 * sizeof(char)); - if(skinNamesAlreadyUsed == NULL) { - G_Error("Was unable to allocate %i bytes.\n", 16 * 128 * sizeof(char)); - return; - } - - memset(skinsForRace, 0, MAX_SKINS_FOR_RACE * 128 * sizeof(char)); - memset(skinNamesAlreadyUsed, 0, 16 * 128 * sizeof(char)); - - // first up, check to see if we want to select a skin from someone that's already playing on this guys team - skin_count_check = g_random_skin_limit.integer; - if (skin_count_check != 0) { - // sanity check the skins to compare against count - if (skin_count_check > 16) { - skin_count_check = 16; - } - - // now construct an array of the names already used - for ( ; iinuse || i == clientNum) { - continue; - } - - // no, so look at the next one, and see if it's in the list we are constructing - // same team? - if (ent->client != NULL && ent->client->sess.sessionTeam == current_team) { - userinfo = malloc(MAX_INFO_STRING * sizeof(char)); - if(userinfo == NULL) { - G_Error("Was unable to allocate %i bytes.\n", MAX_INFO_STRING * sizeof(char)); - return; - } - // so what's this clients model then? - trap_GetUserinfo( i, userinfo, MAX_INFO_STRING * sizeof(char) ); - Q_strncpyz( temp_model, Info_ValueForKey (userinfo, "model"), sizeof( temp_model ) ); - - free(userinfo); - - // check the name - for (x = 0; x< current_skin_count; x++) { - // are we the same? - if (Q_stricmp(skinNamesAlreadyUsed[x], temp_model) == 0) { - // yeah - ok we already got this one - break; - } - } - - // ok, did we match anything? - if (x == current_skin_count) { - // no - better add this name in - Q_strncpyz(skinNamesAlreadyUsed[current_skin_count], temp_model, sizeof(skinNamesAlreadyUsed[current_skin_count])); - current_skin_count++; - } - } - } - - // ok, array constructed. Did we get enough? - if (current_skin_count >= skin_count_check) { - // yeah, we did - so select a skin from one of these then - temp = rand() % current_skin_count; - Q_strncpyz( model, skinNamesAlreadyUsed[temp], MAX_QPATH ); - ForceClientSkin(model, ""); - free(skinNamesAlreadyUsed); - free(skinsForRace); - return; - } - } - - // search through each and every skin we can find - for (i=0; i 0) { - temp = rand() % howManySkins; - Q_strncpyz( model, skinsForRace[temp], MAX_QPATH ); - } else { - model[0] = 0; - } - - free(skinsForRace); - free(skinNamesAlreadyUsed); -} - -/* -=========== -getNewSkin - -Go away and actually get a random new skin based on a group name -============ -*/ -/** -* Go away and actually get a random new skin based on a group name -*/ -static qboolean getNewSkin(const char* group, char* model, const char* color, const gclient_t* client, int32_t clientNum) -{ - char* temp_string = NULL; - - // go away and get what ever races this skin is attached to. - // remove blue or red name - ForceClientSkin(model, ""); - - temp_string = G_searchGroupList(model); - - // are any of the races legal for this team race? - if (legalSkin(temp_string, group)) { - ForceClientSkin(model, color); - return qfalse; - } - - //if we got this far, then we need to reset the skin to something appropriate - randomSkin(group, model, client->sess.sessionTeam, clientNum); - return qtrue; -} - -/* -=========== -G_Client_UserinfoChanged -============ -*/ -/** -* Called from G_Client_Connect when the player first connects and -* directly by the server system when the player updates a userinfo variable. -* -* The game can override any of the settings and call trap_SetUserinfo -* if desired. -*/ -void G_Client_UserinfoChanged( int32_t clientNum ) { - gentity_t* ent = NULL; - int32_t i = 0; - int32_t modelOffset = 0; - char* s = NULL; - char model[MAX_QPATH]; - char oldname[MAX_STRING_CHARS]; - char userinfo[MAX_INFO_STRING]; - char age[MAX_NAME_LENGTH]; - char race[MAX_NAME_LENGTH]; - char sHeight[10]; - char sWeight[10]; - double weight; - double height; - gclient_t* client = NULL; - qboolean reset; - qboolean changeName = qtrue; //TiM : For the name filter - clientPersistant_t* pers = NULL; - clientSession_t* sess = NULL; - - ent = g_entities + clientNum; - if(ent == NULL) { - return; - } - - memset(model, 0, sizeof(model)); - memset(oldname, 0, sizeof(oldname)); - memset(userinfo, 0, sizeof(userinfo)); - memset(age, 0, sizeof(age)); - memset(race, 0, sizeof(race)); - memset(sHeight, 0, sizeof(sHeight)); - memset(sWeight, 0, sizeof(sWeight)); - client = ent->client; - pers = &client->pers; - sess = &client->sess; - - //TiM - Exit if this user has had their info clamped - if ( (ent->flags & FL_CLAMPED) != 0 ) { - return; - } - - trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); - - // check for malformed or illegal info strings - if ( !Info_Validate(userinfo) ) { - strcpy (userinfo, "\\name\\badinfo"); - } - - // check for local client - s = Info_ValueForKey( userinfo, "ip" ); - if ( strcmp( s, "localhost" ) == 0 ) { - pers->localClient = qtrue; - } - - // check the item prediction - s = Info_ValueForKey( userinfo, "cg_predictItems" ); - if ( atoi( s ) == 0 ) { - pers->predictItemPickup = qfalse; - } else { - pers->predictItemPickup = qtrue; - } - - // set name - //TiM: Filter for if a player is already on this server with that name. - s = Info_ValueForKey (userinfo, "name"); - - if ( (rpg_uniqueNames.integer != 0) && (( ent->r.svFlags & SVF_BOT ) == 0) ) { - char newName[36]; - char activeName[36]; - - memset(newName, 0, sizeof(newName)); - memset(activeName, 0, sizeof(activeName)); - ClientCleanName( s, newName, sizeof(newName) ); - Q_CleanStr( newName ); - - //loop thru all the clients, and see if we have one that has the same name as our proposed one - for ( i = 0; i < level.numConnectedClients; i++ ) { - Q_strncpyz( activeName, g_entities[i].client->pers.netname, sizeof( activeName ) ); - Q_CleanStr( activeName ); - - if ( (g_entities[i].client->ps.clientNum != client->ps.clientNum) - && (Q_stricmp( newName, activeName ) == 0) ) { - trap_SendServerCommand( ent-g_entities, " print \"Unable to change name. A player already has that name on this server.\n\" "); - changeName = qfalse; - break; - } - } - } - - if ( changeName ) { - Q_strncpyz ( oldname, pers->netname, sizeof( oldname ) ); - ClientCleanName( s, pers->netname, sizeof(pers->netname) ); - - if ( sess->sessionTeam == TEAM_SPECTATOR ) { - if ( sess->spectatorState == SPECTATOR_SCOREBOARD ) { - Q_strncpyz( pers->netname, "scoreboard", sizeof(pers->netname) ); - } - } - - if ( pers->connected == CON_CONNECTED ) { - if ( strcmp( oldname, pers->netname ) != 0) { - if ( (!levelExiting) && (level.intermissiontime == 0) ) { - //no need to do this during level changes - trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " renamed to %s\n\"", oldname, pers->netname) ); - } - } - } - } - - pers->pms_height = atof( Info_ValueForKey( userinfo, "height" ) ); - if ( pers->pms_height == 0 ) { - pers->pms_height = 1.0f; - } - - pers->maxHealth = atoi( Info_ValueForKey( userinfo, "handicap" ) ); - if ( pers->maxHealth < 1 || pers->maxHealth > 100 ) { - pers->maxHealth = 100; - } - //if you have a class, ignores handicap and 100 limit, sorry - client->ps.stats[STAT_MAX_HEALTH] = pers->maxHealth; - - Q_strncpyz( model, Info_ValueForKey (userinfo, "model"), sizeof( model ) ); - - // team - - switch( sess->sessionTeam ) { - case TEAM_RED: - { - // decide if we are going to have to reset a skin cos it's not applicable to a race selected - if ((g_gametype.integer < GT_TEAM) || (Q_stricmp("", g_team_group_red.string) == 0)) { - ForceClientSkin(model, "red"); - break; - } else { - // at this point, we are playing CTF and there IS a race specified for this game - reset = getNewSkin(g_team_group_red.string, model, "red", client, clientNum); - - // did we get a model name back? - if (model[0] == 0) { - // no - this almost certainly means we had a bogus race is the g_team_group_team cvar - // so reset it to starfleet and try it again - Com_Printf("WARNING! - Red Group %s is unknown - resetting Red Group to Allow Any Group\n", g_team_group_red.string); - trap_Cvar_Set("g_team_group_red", ""); - trap_Cvar_Register( &g_team_group_red, "g_team_group_red", - "", CVAR_LATCH); - - // Since we are allow any group now, just get his normal model and carry on - Q_strncpyz( model, Info_ValueForKey (userinfo, "model"), sizeof( model ) ); - ForceClientSkin(model, "red"); - reset = qfalse; - } - - if (reset) { - if ( !levelExiting ) { - //no need to do this during level changes - trap_SendServerCommand( -1, va("print \"In-appropriate skin selected for %s on team %s\nSkin selection overridden from skin %s to skin %s\n\"", - pers->netname, g_team_group_red.string, Info_ValueForKey (userinfo, "model"), model)); - } - ForceClientSkin(model, "red"); - // change the value in out local copy, then update it on the server - Info_SetValueForKey(userinfo, "model", model); - trap_SetUserinfo(clientNum, userinfo); - } - break; - } - } - case TEAM_BLUE: - { - // decide if we are going to have to reset a skin cos it's not applicable to a race selected - if ((g_gametype.integer < GT_TEAM) || (Q_stricmp("", g_team_group_blue.string) == 0)) { - ForceClientSkin(model, "blue"); - break; - } else { - // at this point, we are playing CTF and there IS a race specified for this game - // go away and get what ever races this skin is attached to. - reset = getNewSkin(g_team_group_blue.string, model, "blue", client, clientNum); - - // did we get a model name back? - if (model[0] == 0) { - // no - this almost certainly means we had a bogus race is the g_team_group_team cvar - // so reset it to klingon and try it again - Com_Printf("WARNING! - Blue Group %s is unknown - resetting Blue Group to Allow Any Group\n", g_team_group_blue.string); - trap_Cvar_Set("g_team_group_blue", ""); - trap_Cvar_Register( &g_team_group_blue, "g_team_group_blue", - "", CVAR_LATCH ); - - // Since we are allow any group now, just get his normal model and carry on - Q_strncpyz( model, Info_ValueForKey (userinfo, "model"), sizeof( model ) ); - ForceClientSkin(model, "blue"); - reset = qfalse; - } - - if (reset) { - if ( !levelExiting ) { - //no need to do this during level changes - trap_SendServerCommand( -1, va("print \"In-appropriate skin selected for %s on team %s\nSkin selection overridden from skin %s to skin %s\n\"", - pers->netname, g_team_group_blue.string, Info_ValueForKey (userinfo, "model"), model)); - } - ForceClientSkin(model, "blue"); - // change the value in out local copy, then update it on the server - Info_SetValueForKey(userinfo, "model", model); - trap_SetUserinfo(clientNum, userinfo); - } - break; - } - } - default: - break; - } - - if((g_gametype.integer >= GT_TEAM) && (sess->sessionTeam == TEAM_SPECTATOR)) { - // don't ever use a default skin in teamplay, it would just waste memory - ForceClientSkin(model, "red"); - } - - if((rpg_rpg.integer != 0) && (rpg_forceclasscolor.integer != 0) && (g_gametype.integer < GT_TEAM)) { - ForceClientSkin( model, g_classData[sess->sessionClass].modelSkin ); - } - - //TiM : For when an admin chooses not to see admin messages - //Marcin : and check for privacy mode - 24/12/2008 - s = Info_ValueForKey( userinfo, "noAdminChat" ); - if ( atoi( s ) > 0 ) { - client->noAdminChat = qtrue; - } else { - client->noAdminChat = qfalse; - } - - // teamInfo - s = Info_ValueForKey( userinfo, "teamoverlay" ); - if ( ! *s || atoi( s ) != 0 ) { - pers->teamInfo = qtrue; - } else { - pers->teamInfo = qfalse; - } - - //PMS system - lock down the values - s = Info_ValueForKey( userinfo, "height" ); - height = atof( s ); - if (height > (float)rpg_maxHeight.value ) { - Q_strncpyz( sHeight, rpg_maxHeight.string, sizeof( sHeight ) ); - } else if (height < (float)rpg_minHeight.value ) { - Q_strncpyz( sHeight, rpg_minHeight.string, sizeof( sHeight ) ); - } else { - Q_strncpyz( sHeight, s, sizeof( sHeight ) ); - } - - //TiM - needed for height offset - pers->pms_height = atof( sHeight ); - - //PMS system - lock down the values - s = Info_ValueForKey( userinfo, "weight" ); - weight = atof( s ); - if (weight > (float)rpg_maxWeight.value ) { - Q_strncpyz( sWeight, rpg_maxWeight.string, sizeof( sWeight ) ); - } else if (weight < (float)rpg_minWeight.value ) { - Q_strncpyz( sWeight, rpg_minWeight.string, sizeof( sWeight ) ); - } else { - Q_strncpyz( sWeight, s, sizeof( sWeight ) ); - } - - s = Info_ValueForKey( userinfo, "age" ); - Q_strncpyz( age, s, sizeof(age) ); - - s = Info_ValueForKey( userinfo, "race" ); - Q_strncpyz( race, s, sizeof( race ) ); - - s = Info_ValueForKey( userinfo, "modelOffset" ); - modelOffset = atoi( s ); - - // send over a subset of the userinfo keys so other clients can - // print scoreboards, display models, and play custom sounds - //FIXME: In future, we'll lock down these PMS values so we can't have overloaded transmission data - if ( (ent->r.svFlags & SVF_BOT) != 0 ) { - s = va("n\\%s\\t\\%i\\p\\%i\\model\\%s\\hc\\%i\\w\\%i\\l\\%i\\skill\\%s\\age\\25\\height\\%s\\weight\\%s\\race\\Bot\\of\\%i\\admin\\0", - pers->netname, sess->sessionTeam, sess->sessionClass, model, - pers->maxHealth, sess->wins, sess->losses, - Info_ValueForKey( userinfo, "skill" ), - sHeight, sWeight, modelOffset ); - } else { - s = va("n\\%s\\t\\%i\\p\\%i\\model\\%s\\hc\\%i\\w\\%i\\l\\%i\\age\\%s\\height\\%s\\weight\\%s\\race\\%s\\of\\%i\\admin\\%i", - pers->netname, sess->sessionTeam, sess->sessionClass, model, - pers->maxHealth, sess->wins, sess->losses, age, - sHeight, sWeight, race, modelOffset, ((int)G_Client_IsAdmin(g_entities+clientNum))); - } - - trap_SetConfigstring( CS_PLAYERS+clientNum, s ); - - G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, s ); // no ip logging here as string might get to long -} - - -/* -=========== -G_Client_Connect -============ -*/ -/** -* Called when a player begins connecting to the server. -* Called again for every map change or tournement restart. -* -* The session information will be valid after exit. -* -* Return NULL if the client should be allowed, otherwise return -* a string with the reason for denial. -* -* Otherwise, the client will be sent the current gamestate -* and will eventually get to G_Client_Begin. -* -* firstTime will be qtrue the very first time a client connects -* to the server machine, but qfalse on map changes and tournement -* restarts. -*/ -char* G_Client_Connect( int32_t clientNum, qboolean firstTime, qboolean isBot ) { - char* value = NULL; - char* newClass = NULL; - char* newRank = NULL; - char ip[64]; //TiM : Saved the IP data for player recon feature - char userinfo[MAX_INFO_STRING]; - gclient_t* client = NULL; - gentity_t* ent = NULL; - vmCvar_t mapname; - vmCvar_t sv_hostname; - - ent = &g_entities[ clientNum ]; - if(ent == NULL) { - return "Critical Error: Client entity was NULL"; - } - - memset(ip, 0, sizeof(ip)); - memset(userinfo, 0, sizeof(userinfo)); - - trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM ); - trap_Cvar_Register( &sv_hostname, "sv_hostname", "", CVAR_SERVERINFO | CVAR_ROM ); - - trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); - - // check to see if they are on the banned IP list - value = Info_ValueForKey (userinfo, "ip"); - - Q_strncpyz( ip, value, sizeof(ip) ); - - if ( G_FilterPacket( value ) || CheckID( Info_ValueForKey(userinfo, "sv_securityCode" ) ) ) { - return "Banned from this server"; - } - - // check for a password - if ( !isBot ) - { - value = Info_ValueForKey (userinfo, "password"); - if ( g_password.string[0] && Q_stricmp( g_password.string, "none" ) != 0 && strcmp( g_password.string, value) != 0) - { - return "Invalid password"; - } - } - - //TiM: If need be, chack to see if any other players have the current name... - //evil impersonators and the likes - if ( rpg_uniqueNames.integer != 0 && !isBot ) { - char name[36]; - char oldName[36]; - int32_t i = 0; - - memset(name, 0, sizeof(name)); - memset(oldName, 0, sizeof(oldName)); - - //get the name - value = Info_ValueForKey( userinfo, "name" ); - //Clean the data - ClientCleanName( value, name, sizeof( name ) ); - //Now, do a compare with all clients in the server - for (i = 0; i < MAX_CLIENTS; i++ ) { - if ( (g_entities[i].client == NULL) || (g_entities[i].client->pers.connected != CON_CONNECTED) ) { - continue; - } - - if ( g_entities[i].client->pers.netname[0] ) { - - //local copy the string and work on that, else we risk wrecking other people's names - Q_strncpyz( oldName, g_entities[i].client->pers.netname, sizeof( oldName ) ); - if ( (Q_stricmp( Q_CleanStr(name), Q_CleanStr(oldName) ) == 0) && !isBot ) { - return "There is already a user with that name."; - } - } - } - } - - // they can connect - ent->client = level.clients + clientNum; - client = ent->client; - - memset( client, 0, sizeof(*client) ); - - client->pers.connected = CON_CONNECTING; - - // read or initialize the session data - if ( firstTime || level.newSession ) { - G_InitSessionData( client, userinfo ); - } - G_ReadSessionData( client ); - - if( isBot ) { - ent->r.svFlags |= SVF_BOT; - ent->inuse = qtrue; - if( !G_BotConnect( clientNum, !firstTime ) ) { - return "BotConnectfailed"; - } - } - - // get and distribute relevent paramters - G_LogPrintf( "ClientConnect: %i (%s)\n", clientNum, g_entities[clientNum].client->pers.ip ); - if ( rpg_rpg.integer != 0 /*&& firstTime*/ ) - { - //TiM: Code for automatic class + rank switching - //======================================================== - if ( isBot ) { - client->sess.sessionClass = 0; - client->ps.persistant[PERS_SCORE] = 1; - } else { - int32_t tmpScore = 0; - int32_t i = 0; - qboolean changeRank = qfalse; - - newClass = Info_ValueForKey (userinfo, "ui_playerClass" ); - newRank = Info_ValueForKey (userinfo, "ui_playerRank" ); - - //Com_Printf( S_COLOR_RED "Data: %s %s\n", newClass, newRank ); - - if ( newClass[0] != 0 ) { - client->sess.sessionClass = ValueNameForClass ( newClass ); //TiM: BOOYEAH! :) - //if class doesn't exist, default to 0 - if ( client->sess.sessionClass < 0 ) { - client->sess.sessionClass = 0; - } - } else { - client->sess.sessionClass = 0; - } - - for (i = 0; i < MAX_RANKS; i++ ) { - if ( rpg_startingRank.string[0] == 0 && newRank[0] != 0 ) { - if ( Q_stricmp( newRank, g_rankNames[i].consoleName ) == 0 ) { - tmpScore = i;//1 << i; - - if ( rpg_changeRanks.integer != 0 ) { - changeRank = qtrue; - } - break; - } - } else { - if ((rpg_startingRank.string[0] != 0) && (Q_stricmp( g_rankNames[i].consoleName, rpg_startingRank.string ) == 0)) { - tmpScore = i;// 1 << i; - changeRank = qtrue; - break; - } - } - } - - if ( changeRank ) { - ent->client->UpdateScore = qtrue; - G_Client_SetScore( ent, tmpScore ); - } - } - - //======================================================== - } - G_Client_UserinfoChanged( clientNum ); - - //RPG-X: Save the ip for later - has to be down here, since it gets flushed in the above function - Q_strncpyz( ent->client->pers.ip, ip, sizeof( ent->client->pers.ip ) ); - - // don't do the "xxx connected" messages if they were caried over from previous level - if ( firstTime ) - { - if ( !levelExiting ) - {//no need to do this during level changes - qboolean nameFound = qfalse; - - //Check to see if this player already connected on this server - if ( (rpg_renamedPlayers.integer != 0) && ((ent->r.svFlags & SVF_BOT) == 0) ) { - int32_t i = 0; - - for ( i = 0; i < MAX_RECON_NAMES; i++ ) { - if ( g_reconData[i].previousName[0] == 0 ) { - continue; - } - - if ( (Q_stricmp( client->pers.ip, g_reconData[i].ipAddress ) == 0) - && (Q_stricmp( client->pers.netname, g_reconData[i].previousName ) != 0 )) { - trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " (With the previous name of %s" S_COLOR_WHITE ") connected\n\"", client->pers.netname, g_reconData[i].previousName) ); - nameFound = qtrue; - break; - } - } - } - - if ( !nameFound ) { - trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " connected\n\"", client->pers.netname) ); - } - - //RPG-X | Phenix | 07/04/2005 - client->AdminFailed = 0; - ent->n00bCount = 0; - client->LoggedAsDeveloper = qfalse; - } - } - - if ( (g_gametype.integer >= GT_TEAM) && (client->sess.sessionTeam != TEAM_SPECTATOR )) { - BroadcastTeamChange( client, -1 ); - } - - // count current clients and rank for scoreboard - //G_Client_CalculateRanks( qfalse ); - - //RPG-X: J2J - Reset Variables - DragDat[clientNum].AdminId = -1; - DragDat[clientNum].distance = 0; - g_entities[clientNum].client->noclip = qfalse; - - return NULL; -} - -extern holoData_t holoData; - -//! Think function for temporal entity that transmits the holodeck date to the client -static void holoTent_think(gentity_t* ent) { - if(ent->count == 0) { - trap_SendServerCommand(ent-g_entities, va("holo_data %i", holoData.numProgs)); - ent->count = 1; - ent->health = 0; - ent->nextthink = level.time + 250; - return; - } - if(ent->health == holoData.numProgs) { - ent->count++; - ent->health = 0; - } - switch(ent->count) { - case 1: // name - trap_SendServerCommand(ent-g_entities, va("holo_data \"n%i\\%s\\\"", ent->health, holoData.name[ent->health])); - break; - case 2: // desc1 - trap_SendServerCommand(ent-g_entities, va("holo_data \"da%i\\%s\\\"", ent->health, holoData.desc1[ent->health])); - break; - case 3: // desc2 - trap_SendServerCommand(ent-g_entities, va("holo_data \"db%i\\%s\\\"", ent->health, holoData.desc2[ent->health])); - break; - case 4: // image - trap_SendServerCommand(ent-g_entities, va("holo_data \"i%i\\%s\\\"", ent->health, holoData.image[ent->health])); - break; - } - ent->health++; - if(ent->count > 4) { - G_PrintfClient(ent, "Received data of %i holodeck programs.\n", holoData.numProgs); - G_FreeEntity(ent); - return; - } - ent->nextthink = level.time + 250; -} - -//! Create a temporal entity that sends over the holodata to the client -static void G_SendHoloData(int32_t clientNum) { - gentity_t *holoTent; - - holoTent = G_Spawn(); - holoTent->classname = "holoTent"; - - holoTent->target_ent = g_entities + clientNum; - - holoTent->think = holoTent_think; - holoTent->nextthink = level.time + 2500; -} - -//! Think function for temporal entity that transmits the server change data and map change data for transporter UI -static void transTent_think(gentity_t* ent) { - char temp[MAX_STRING_CHARS]; - int32_t i; - - memset(temp, 0, sizeof(temp)); - - for(i = 0; i < level.srvChangeData.count; i++) { - if(temp[0] == 0) { - Com_sprintf(temp, sizeof(temp), "ui_trdata d%i\\%s\\", i, level.srvChangeData.name[i]); - } else { - Com_sprintf(temp, sizeof(temp), "%sd%i\\%s\\", temp, i, level.srvChangeData.name[i]); - } - } - - trap_SendServerCommand(ent->target_ent-g_entities, temp); - G_FreeEntity(ent); -} - -//! creates an entity that transmits the server change data to the client -static void G_SendTransData(int32_t clientNum) { - gentity_t *transTent; - - transTent = G_Spawn(); - transTent->classname = "transTent"; - - transTent->target_ent = g_entities + clientNum; - - transTent->think = transTent_think; - transTent->nextthink = level.time + 500; -} - -/* -=========== -G_Client_Begin -============ -*/ -/** -* called when a client has finished connecting, and is ready -* to be placed into the level. This will happen every level load, -* and on transition between teams, but doesn't happen on respawns -*/ -void G_Client_Begin( int32_t clientNum, qboolean careAboutWarmup, qboolean isBot, qboolean first ) { - gentity_t* ent = NULL; - gclient_t* client = NULL; - gentity_t* tent = NULL; - gentity_t* selfdestruct = NULL; - int32_t flags = 0; - qboolean alreadyIn = qfalse; - int32_t score = 0; - - ent = g_entities + clientNum; - if(ent == NULL) { - return; - } - - if( ent->botDelayBegin ) { - G_QueueBotBegin( clientNum ); - ent->botDelayBegin = qfalse; - return; - } - - client = level.clients + clientNum; - - if ( ent->r.linked ) { - trap_UnlinkEntity( ent ); - } - G_InitGentity( ent ); - ent->touch = 0; - ent->pain = 0; - ent->client = client; - - if ( client->pers.connected == CON_CONNECTED ) { - alreadyIn = qtrue; - } - client->pers.connected = CON_CONNECTED; - client->pers.enterTime = level.time; - client->pers.teamState.state = TEAM_BEGIN; - - // save eflags around this, because changing teams will - // cause this to happen with a valid entity, and we - // want to make sure the teleport bit is set right - // so the viewpoint doesn't interpolate through the - // world to the new position - - //TiM... I think this is why my damn RANK SYSTEM ENHANCEMENT HAS BEEN BUGGING OUT!!@!@!! - //ARRRGRGRGRGRGRGRGRGRGRGRGHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH!!!!! D:< - flags = client->ps.eFlags; - score = client->ps.persistant[PERS_SCORE]; - memset( &client->ps, 0, sizeof( client->ps ) ); - client->ps.eFlags = flags; - - client->UpdateScore = qtrue; - G_Client_SetScore( ent, score ); - - // locate ent at a spawn point - G_Client_Spawn( ent, 0, qfalse );//RPG-X: RedTechie - Modifyed - - if ( client->sess.sessionTeam != TEAM_SPECTATOR) { - // Don't use transporter FX for spectators or those watching the holodoors. - // send event - - ent->client->ps.powerups[PW_QUAD] = level.time + 4000; - tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TRANSPORT_IN ); - tent->s.clientNum = ent->s.clientNum; - } - G_LogPrintf( "ClientBegin: %i (%s)\n", clientNum, g_entities[clientNum].client->pers.ip ); - - // count current clients and rank for scoreboard - G_Client_CalculateRanks( qfalse ); - - //TiM - This appears to be a flaw in Raven's design - //When a client connects, or if they enter admin or medics class - //ensure the relevant health data is sent to them, or else they'll - //see anomalies when scanning players - if ( (client->sess.sessionTeam == TEAM_SPECTATOR) || - (g_classData[client->sess.sessionClass].isMedical) || - (g_classData[client->sess.sessionClass].isAdmin) ) - { - int32_t i = 0; - char entry[16]; - char command[1024]; - int32_t numPlayers = 0; - gentity_t *player; - int32_t len = 0; - int32_t cmdLen=0; - - memset(entry, 0, sizeof(entry)); - memset(command, 0, sizeof(command)); - - for ( ; i < g_maxclients.integer; i++ ) - { - player = g_entities + i; - - if ( (player == NULL) || (player == ent) || !player->inuse ) { - continue; - } - - Com_sprintf( entry, sizeof(entry), " %i %i", i, player->health >= 0 ? player->health : 0 ); - len = strlen( entry ); - if ( cmdLen + len > sizeof( command ) ) { - break; - } - strcpy( command + cmdLen, entry ); - cmdLen += len; - - numPlayers++; - } - - if ( numPlayers > 0 ) { - trap_SendServerCommand( clientNum, va("hinfo %i%s", numPlayers, command) ); - } - } - - //RPG-X: RedTechie - But we dont care about warmup! - if ( careAboutWarmup ) { - if (level.restarted || g_restarted.integer != 0) { - trap_Cvar_Set( "g_restarted", "0" ); - level.restarted = qfalse; - } - } - - //RPG-X | Phenix | 21/11/2004 - //BOOKMARK FOR INIT - if(!alreadyIn) { - // RPG-X | Phenix | 06/04/2005 - ent->client->n00bTime = -1; - } - - ent->client->fraggerTime = -1; - - // kef -- should reset all of our awards-related stuff - G_ClearClientLog(clientNum); - - //TiM - if our user's security key was default, transmit the received IP bak to - //the client and get it to encode it into our new key - - //Scooter's filter list - if( Q_stricmp( ent->client->pers.ip, "localhost" ) != 0 //localhost - && Q_strncmp( ent->client->pers.ip, "10.", 3 ) != 0 //class A - && Q_strncmp( ent->client->pers.ip, "172.16.", 7 ) != 0 //class B - && Q_strncmp( ent->client->pers.ip, "192.168.", 8 ) != 0 //class C - && Q_strncmp( ent->client->pers.ip, "127.", 4 ) != 0 //loopback - && Q_strncmp( ent->client->pers.ip, "169.254.", 8 ) != 0 //link-local - ) - { - char userInfo[MAX_TOKEN_CHARS]; - uint64_t securityID = 0; - - memset(userInfo, 0, sizeof(userInfo)); - - trap_GetUserinfo( clientNum, userInfo, sizeof( userInfo ) ); - if ( userInfo[0] == 0 ) { - return; - } - - securityID = (uint64_t)atoul( Info_ValueForKey( userInfo, "sv_securityCode" ) ); - - if ( (securityID <= 0) || (securityID >= 0xffffffff) ) { - trap_SendServerCommand( clientNum, va( "configID %s", ent->client->pers.ip ) ); - } - } - - // send srv change data to ui - if(!isBot && first) { - if(level.srvChangeData.count > 0) { - G_SendTransData(clientNum); - } - } - - // send holo data to ui - if(!isBot && first) { - if(holoData.numProgs) { - G_SendHoloData(clientNum); - } - } - - //RPG-X: Marcin: show the server motd - 15/12/2008 - if ( !isBot && first ) { - trap_SendServerCommand( ent->s.number, "motd" ); - } - - if ( !isBot ) { - qboolean last = qfalse; - int32_t len = 0; - fileHandle_t file = 0; - char* p = NULL; - char* q = NULL; - char buf[16000]; // TODO move to heap ? - - len = trap_FS_FOpenFile( rpg_motdFile.string, &file, FS_READ ); - if (file == 0 || len == 0) { - trap_SendServerCommand( ent->s.number, va("motd_line \"^1%s not found or empty^7\"", rpg_motdFile.string) ); - return; - } - - memset(buf, 0, sizeof(buf)); - trap_FS_Read( buf, len, file ); - - p = &buf[0]; - q = p; - buf[len] = '\0'; - while ( !last ) { - p = q; - while ( *q != '\n' ) { - if ( !*q ) { - last = qtrue; - } - if ( ( *q == ' ' ) && ( EndWord( q ) - p ) > 78 ) { - break; - } - q++; - } - *q = '\0'; - - trap_SendServerCommand( ent->s.number, va( "motd_line \"%s\"", p ) ); - - q++; - } - - } - //we may currently be selfdestructing, so send stuff for that case - selfdestruct = G_Find(NULL, FOFS(classname), "target_selfdestruct"); - if((selfdestruct != NULL) && ((selfdestruct->spawnflags & 1) != 0)) { - trap_SendServerCommand(ent->s.number, va("selfdestructupdate %i", selfdestruct->damage - level.time)); - } else { - trap_SendServerCommand(ent->s.number, va("selfdestructupdate %i", -1)); - } -} - -/** - * Get weapons for a class. - * - * \param client The client. - * \param pclass Class to get weapons for. - * \author PHENIX1 - */ -static void G_Client_WeaponsForClass ( gclient_t* client, pclass_t pclass ) -{ - int32_t i = WP_1; - int32_t Bits = 0; - - Bits = ( 1 << WP_1); - Bits |= g_classData[pclass].weaponsFlags; - - for ( ; i < MAX_WEAPONS; i++ ) { - //if we want no weapons and aren't an admin, skip this particular weapon - if ( (rpg_noweapons.integer != 0) && !g_classData[pclass].isAdmin ) { - if ( (i >= WP_5) && (i <= WP_10) ) { - continue; - } - } - - if ( (Bits & ( 1 << i )) != 0 ) { - client->ps.stats[STAT_WEAPONS] |= ( 1 << i ); - client->ps.ammo[i] = Min_Weapon(i); - } - } -} - -/** - * Get holdable items for a class. - * - * \param client The client. - * \param pclass Class for which to get holdables. - */ -static void G_Client_HoldablesForClass ( gclient_t* client, pclass_t pclass ) -{ - if ( g_classData[pclass].isMarine ) { - client->ps.stats[STAT_HOLDABLE_ITEM] = BG_FindItemForHoldable( HI_TRANSPORTER ) - bg_itemlist; - } else if ( g_classData[pclass].isAdmin ) { - client->ps.stats[STAT_HOLDABLE_ITEM] = BG_FindItemForHoldable( HI_SHIELD ) - bg_itemlist; - } -} - -void G_Client_StoreClientInitialStatus( gentity_t* ent ) -{ - char userinfo[MAX_INFO_STRING]; - - if ( clientInitialStatus[ent->s.number].initialized ) { - //already set - return; - } - - if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { - //don't store their data if they're just a spectator - return; - } - - memset(userinfo, 0, sizeof(userinfo)); - trap_GetUserinfo( ent->s.number, userinfo, sizeof( userinfo ) ); - Q_strncpyz( clientInitialStatus[ent->s.number].model, Info_ValueForKey (userinfo, "model"), sizeof( clientInitialStatus[ent->s.number].model ) ); - clientInitialStatus[ent->s.number].pClass = ent->client->sess.sessionClass; - clientInitialStatus[ent->s.number].team = ent->client->sess.sessionTeam; - clientInitialStatus[ent->s.number].initialized = qtrue; - ent->client->classChangeDebounceTime = 0; -} - -/* -=========== -G_Client_Spawn - -Called every time a client is placed fresh in the world: -after the first G_Client_Begin, and after each respawn -Initializes all non-persistant parts of playerState ------------------------------------- -Modifyed By: RedTechie -And also by Marcin - 30/12/2008 -============ -*/ -void G_Client_Spawn(gentity_t* ent, int32_t rpgx_spawn, qboolean fromDeath ) { - int32_t index = 0; - int32_t i = 0; - int32_t persistant[MAX_PERSISTANT]; - int32_t flags = 0; - int32_t savedPing = 0; - int32_t cCDT = 0; - int32_t clientNum = 0; - vec3_t spawn_origin; - vec3_t spawn_angles; - gclient_t* client = NULL; - clientPersistant_t saved; - clientSession_t savedSess; - gentity_t* spawnPoint = NULL; - pclass_t pClass = 0; - pclass_t oClass = 0; - - if(ent == NULL) { - return; - } - - memset(persistant, 0, sizeof(persistant)); - index = ent - g_entities; - client = ent->client; - clientNum = ent->client->ps.clientNum; - - // find a spawn point - // do it before setting health back up, so farthest - // ranging doesn't count this client - if(rpgx_spawn != 1){//RPG-X: RedTechie - Make sure the spawn is regular spawn or spawn at current position (rpgx_spawn = current possition) - if ( client->sess.sessionTeam == TEAM_SPECTATOR ) { - spawnPoint = SelectSpectatorSpawnPoint (spawn_origin, spawn_angles); - } else { - do { - // the first spawn should be at a good looking spot - if ( !client->pers.initialSpawn && client->pers.localClient ) { - client->pers.initialSpawn = qtrue; - spawnPoint = SelectInitialSpawnPoint( spawn_origin, spawn_angles ); - } else { - // don't spawn near existing origin if possible - spawnPoint = G_Client_SelectSpawnPoint (client->ps.origin, spawn_origin, spawn_angles); - } - - // Tim needs to prevent bots from spawning at the initial point - // on q3dm0... - if ( (( spawnPoint->flags & FL_NO_BOTS ) != 0) && (( ent->r.svFlags & SVF_BOT ) != 0) ) { - continue; // try again - } - // just to be symetric, we have a nohumans option... - if ( (( spawnPoint->flags & FL_NO_HUMANS ) != 0) && (( ent->r.svFlags & SVF_BOT ) == 0) ) { - continue; // try again - } - - break; - - } while ( 1 ); - } - } //RPG-X: RedTechie - End rpgx_spawn check - client->pers.teamState.state = TEAM_ACTIVE; - - // toggle the teleport bit so the client knows to not lerp - if(rpgx_spawn != 1){ - flags = ent->client->ps.eFlags & EF_TELEPORT_BIT; - flags ^= EF_TELEPORT_BIT; - } - - // clear everything but the persistant data - - saved = client->pers; - savedSess = client->sess; - savedPing = client->ps.ping; - for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) { - persistant[i] = client->ps.persistant[i]; - } - //okay, this is hacky, but we need to keep track of this, even if uninitialized first time you spawn, it will be stomped anyway - //RPG-X: RedTechie - Damn thing screwed my function up - if(rpgx_spawn != 1){ - if ( client->classChangeDebounceTime ) { - cCDT = client->classChangeDebounceTime; - } - memset (client, 0, sizeof(*client)); - client->classChangeDebounceTime = cCDT; - } - // - client->pers = saved; - client->sess = savedSess; - client->ps.ping = savedPing; - for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) { - client->ps.persistant[i] = persistant[i]; - } - - // increment the spawncount so the client will detect the respawn - if(rpgx_spawn != 1) { - client->ps.persistant[PERS_SPAWN_COUNT]++; - client->airOutTime = level.time + 12000; - } - - if(client->sess.sessionTeam != TEAM_SPECTATOR) { - client->sess.sessionTeam = TEAM_FREE; - } - client->ps.persistant[PERS_TEAM] = client->sess.sessionTeam; - - // clear entity values - client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; - client->ps.eFlags = flags; - client->streakCount = 0; - - ent->client->ps.pm_type = PM_NORMAL; - ent->s.groundEntityNum = ENTITYNUM_NONE; - ent->client = &level.clients[index]; - ent->takedamage = qtrue; - ent->inuse = qtrue; - ent->classname = "player"; - - ent->r.contents = CONTENTS_BODY; - - ent->clipmask = MASK_PLAYERSOLID; - ent->die = G_Client_Die; - ent->waterlevel = 0; - ent->watertype = 0; - ent->flags = 0; - - if(rpgx_spawn != 1) { - VectorCopy (playerMins, ent->r.mins); - VectorCopy (playerMaxs, ent->r.maxs); - } - - client->ps.clientNum = index; - - // health will count down towards max_health - ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] * 1.25; - - oClass = client->sess.sessionClass; - - if ( oClass != client->sess.sessionClass ) { - //need to send the class change - G_Client_UserinfoChanged( client->ps.clientNum ); - } - - client->ps.persistant[PERS_CLASS] = client->sess.sessionClass; - pClass = client->sess.sessionClass; - - if ( pClass != 0 ) { - //no health boost on spawn for playerclasses - ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; - } - - if ( !fromDeath || (rpg_dropOnDeath.integer == 0) || (rpg_allowWeaponDrop.integer == 0) ) { - G_Client_WeaponsForClass( client, pClass ); - } else { // Marcin: just a hand - G_Client_WeaponsForClass( client, 0 ); - } - G_Client_HoldablesForClass( client, pClass ); - - if(rpgx_spawn != 1) { - G_SetOrigin( ent, spawn_origin ); - VectorCopy( spawn_origin, client->ps.origin ); - } - - // the respawned flag will be cleared after the attack and jump keys come up - if(rpgx_spawn != 1) { - client->ps.pm_flags |= PMF_RESPAWNED; - } - - trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd ); - if(rpgx_spawn != 1){ - G_Client_SetViewAngle( ent, spawn_angles ); - } - - if(rpgx_spawn != 1) { - if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { - - } else { - G_MoveBox( ent ); - trap_LinkEntity (ent); - - // force the base weapon up - client->ps.weapon = WP_1; //TiM: WP_5 - client->ps.weaponstate = WEAPON_READY; - - } - } - - // don't allow full run speed for a bit - client->ps.pm_flags |= PMF_TIME_KNOCKBACK; - client->ps.pm_time = 100; - - if(rpgx_spawn != 1) { - client->respawnTime = level.time; - } - - client->inactivityTime = level.time + g_inactivity.integer * 1000; - client->latched_buttons = 0; - - // set default animations - if (rpgx_spawn != 1 ) { - client->ps.stats[TORSOANIM] = BOTH_STAND1; - client->ps.stats[LEGSANIM] = BOTH_STAND1; - } - - if ( level.intermissiontime ) { - MoveClientToIntermission( ent ); - } else { - // fire the targets of the spawn point - if(rpgx_spawn != 1) { - G_UseTargets( spawnPoint, ent ); - } - - // select the highest weapon number available, after any - // spawn given items have fired - client->ps.weapon = 1; - - //TiM - Always default to the null hand - client->ps.weapon = WP_1; - } - - // run a client frame to drop exactly to the floor, - // initialize animations and other things - client->ps.commandTime = level.time - 100; - ent->client->pers.cmd.serverTime = level.time; - ClientThink( ent-g_entities ); - - // positively link the client, even if the command times are weird - if ( ent->client->sess.sessionTeam != TEAM_SPECTATOR ) { - BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); - if(rpgx_spawn != 1) { - VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); - } - trap_LinkEntity( ent ); - - } - - // run the presend to set anything else - ClientEndFrame( ent ); - - // clear entity state values - BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); - - //start-up messages - //FIXME: externalize all this text! - //FIXME: make the gametype titles be graphics! - //FIXME: make it do this on a map_restart also - if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { - //spectators just get the title of the game - switch ( g_gametype.integer ) { - case GT_FFA: // free for all - trap_SendServerCommand( ent-g_entities, va("cp \"%s\"", rpg_welcomemessage.string ) ); - break; - case GT_TOURNAMENT: // one on one tournament - trap_SendServerCommand( ent-g_entities, va("cp \"%s\"", rpg_welcomemessage.string ) ); - break; - case GT_SINGLE_PLAYER: // single player tournament - trap_SendServerCommand( ent-g_entities, va("cp \"%s\"", rpg_welcomemessage.string ) ); - break; - case GT_TEAM: // team deathmatch - trap_SendServerCommand( ent-g_entities, va("cp \"%s\"", rpg_welcomemessage.string ) ); - break; - case GT_CTF: // capture the flag - trap_SendServerCommand( ent-g_entities, va("cp \"%s\"", rpg_welcomemessage.string ) ); - break; - } - } else { - if ( !clientInitialStatus[ent->s.number].initialized ) { - //first time coming in - switch ( g_gametype.integer ) { - case GT_FFA: // free for all - trap_SendServerCommand( ent-g_entities, va("cp \"%s\"", rpg_welcomemessage.string ) ); - break; - case GT_TOURNAMENT: // one on one tournament - trap_SendServerCommand( ent-g_entities, va("cp \"%s\"", rpg_welcomemessage.string ) ); - break; - case GT_SINGLE_PLAYER: // single player tournament - trap_SendServerCommand( ent-g_entities, va("cp \"%s\"", rpg_welcomemessage.string ) ); - break; - case GT_TEAM: // team deathmatch - trap_SendServerCommand( ent-g_entities, va("cp \"%s\"", rpg_welcomemessage.string ) ); - break; - case GT_CTF: // capture the flag - trap_SendServerCommand( ent-g_entities, va("cp \"%s\"", rpg_welcomemessage.string ) ); - break; - } - } - } - - if(rpgx_spawn != 0) { - if ( (client->sess.sessionTeam == TEAM_SPECTATOR) || - g_classData[client->sess.sessionClass].isMedical || - g_classData[client->sess.sessionClass].isAdmin ) { - int32_t l = 0; - int32_t numPlayers = 0; - int32_t len = 0; - int32_t cmdLen=0; - char entry[16]; - char command[1024]; - gentity_t* player = NULL; - - memset(entry, 0, sizeof(entry)); - memset(command, 0, sizeof(entry)); - - for ( ; l < g_maxclients.integer; l++ ) - { - player = g_entities + l; - - if ( (player == NULL) || (player == ent) || !player->inuse ) { - continue; - } - - Com_sprintf( entry, sizeof(entry), " %i %i", l, player->health >= 0 ? player->health : 0 ); - len = strlen( entry ); - if ( cmdLen + len > sizeof( command ) ) { - break; - } - strcpy( command + cmdLen, entry ); - cmdLen += len; - - numPlayers++; - } - - if ( numPlayers > 0 ) { - trap_SendServerCommand( clientNum, va("hinfo %i%s", numPlayers, command) ); - } - } - } - - //store intial client values - //FIXME: when purposely change teams, this gets confused? - - G_Client_StoreClientInitialStatus( ent ); - - //RPG-X: Marcin: stuff was here previously - 22/12/2008 -} - -/* -=========== -G_Client_Disconnect - -Called when a player drops from the server. -Will not be called between levels. - -This should NOT be called directly by any game logic, -call trap_DropClient(), which will call this and do -server system housekeeping. -============ -*/ -void G_Client_Disconnect( int32_t clientNum ) { - gentity_t* ent = NULL; - gentity_t* tent = NULL; - int32_t i = 0; - - ent = &g_entities[clientNum]; - if ( (ent == NULL) || (ent->client == NULL) ) { - return; - } - - // stop any following clients - for ( i = 0 ; i < level.maxclients ; i++ ) { - if ( level.clients[i].sess.sessionTeam == TEAM_SPECTATOR - && level.clients[i].sess.spectatorState == SPECTATOR_FOLLOW - && level.clients[i].sess.spectatorClient == clientNum ) { - StopFollowing( &g_entities[i] ); - } - } - - //RPG-X: J2J - Stop any dragging. - DragDat[clientNum].AdminId = -1; - DragDat[clientNum].distance = 0; - g_entities[clientNum].client->noclip = qfalse; - - //TiM: Log the player's IP and name. If they reconnect again, it'll announce their deceipt >:) - if ( (rpg_renamedPlayers.integer != 0) && ((ent->r.svFlags & SVF_BOT) == 0) ) { - int32_t l = 0; - qboolean foundName = qfalse; - - //Do a chek to see if this player has disconnected b4. else we'll be wasting a slot. - for ( ; l < MAX_RECON_NAMES; l++ ) { - if ( g_reconData[l].ipAddress[0] == 0 ) { - continue; - } - - if ( Q_stricmp( ent->client->pers.ip, g_reconData[l].ipAddress ) == 0 ) { - foundName=qtrue; - break; - } - } - - if ( foundName ) { - memset( &g_reconData[l], 0, sizeof( g_reconData[l] ) ); - - //IP Address - Q_strncpyz( g_reconData[l].ipAddress, ent->client->pers.ip, sizeof( g_reconData[l].ipAddress ) ); - //Player Name - Q_strncpyz( g_reconData[l].previousName, ent->client->pers.netname, sizeof( g_reconData[l].previousName ) ); - } else { - memset( &g_reconData[g_reconNum], 0, sizeof( g_reconData[g_reconNum] ) ); - - //IP Address - Q_strncpyz( g_reconData[g_reconNum].ipAddress, ent->client->pers.ip, sizeof( g_reconData[g_reconNum].ipAddress ) ); - //Player Name - Q_strncpyz( g_reconData[g_reconNum].previousName, ent->client->pers.netname, sizeof( g_reconData[g_reconNum].previousName ) ); - - g_reconNum++; - //cap reconNum just in case. - - if ( g_reconNum >= MAX_RECON_NAMES ) { - g_reconNum = 0; - } - } - } - - // send effect if they were completely connected - if ( (ent->client->pers.connected == CON_CONNECTED) - && (ent->client->sess.sessionTeam != TEAM_SPECTATOR) ) { - vec3_t org; - - VectorCopy( ent->client->ps.origin, org ); - org[2] += (ent->client->ps.viewheight >> 1); - - tent = G_TempEntity( org, EV_PLAYER_TELEPORT_OUT ); - tent->s.clientNum = ent->s.clientNum; - - // They don't get to take powerups with them! - // Especially important for stuff like CTF flags - G_Client_TossClientItems ( ent, qtrue ); - } - G_LogPrintf( "ClientDisconnect: %i (%s)\n", clientNum, g_entities[clientNum].client->pers.ip ); - - // if we are playing in tourney mode and losing, give a win to the other player - if ( (g_gametype.integer == GT_TOURNAMENT) && (level.intermissiontime == 0) - && (level.warmupTime == 0) && (level.sortedClients[1] == clientNum) ) { - level.clients[ level.sortedClients[0] ].sess.wins++; - G_Client_UserinfoChanged( level.sortedClients[0] ); - } - - if ( (g_gametype.integer == GT_TOURNAMENT) && (ent->client->sess.sessionTeam == TEAM_FREE) && (level.intermissiontime != 0) ) { - trap_SendConsoleCommand( EXEC_APPEND, "map_restart 0\n" ); - level.restarted = qtrue; - level.intermissiontime = 0; - } - - trap_UnlinkEntity (ent); - memset( ent, 0, sizeof( ent ) ); - ent->s.modelindex = 0; - ent->inuse = qfalse; - ent->classname = "disconnected"; - ent->client->pers.connected = CON_DISCONNECTED; - ent->client->ps.persistant[PERS_TEAM] = TEAM_FREE; - ent->client->ps.persistant[PERS_CLASS] = 0;//PC_NOCLASS; - ent->client->sess.sessionTeam = TEAM_FREE; - ent->client->sess.sessionClass = 0;//PC_NOCLASS; - - trap_SetConfigstring( CS_PLAYERS + clientNum, ""); - - G_Client_CalculateRanks( qfalse ); - - if ( (ent->r.svFlags & SVF_BOT) != 0 ) { - BotAIShutdownClient( clientNum ); - } - - // kef -- if this guy contributed to any of our kills/deaths/weapons logs, clean 'em out - G_ClearClientLog(clientNum); - - //also remove any initial data - clientInitialStatus[clientNum].initialized = qfalse; -} - -/* -================ -IsAdmin -RPG-X | Phenix | 21/11/2004 -================ -*/ -/** -* Checks if player is an admin. -* \param ent the player -* -* \author Ubergames - Phenix -* \date 21/11/2004 -*/ -qboolean G_Client_IsAdmin( gentity_t *ent) -{ - if ( ent == NULL ) { - return qfalse; - } - - if ( ent->client == NULL ) { - return qfalse; - } - - if (( g_classData[ent->client->sess.sessionClass].isAdmin ) || - ( ent->client->LoggedAsAdmin == qtrue ) || - ( ent->client->LoggedAsDeveloper == qtrue )) { - return qtrue; - } else { - return qfalse; - } -} - -/* -=========== -G_Client_GetLocation - -Report a location for the player. Uses placed nearby target_location entities -============ -*/ -static gentity_t* G_Client_GetLocation(gentity_t* ent) { - gentity_t* eloc = NULL; - gentity_t* best = NULL; - double bestlen = 0.0; - double len = 0.0; - vec3_t origin; - - bestlen = 3*8192.0*8192.0; - - VectorCopy( ent->r.currentOrigin, origin ); - - for (eloc = level.locationHead; eloc; eloc = eloc->nextTrain) { - len = ( origin[0] - eloc->r.currentOrigin[0] ) * ( origin[0] - eloc->r.currentOrigin[0] ) - + ( origin[1] - eloc->r.currentOrigin[1] ) * ( origin[1] - eloc->r.currentOrigin[1] ) - + ( origin[2] - eloc->r.currentOrigin[2] ) * ( origin[2] - eloc->r.currentOrigin[2] ); - - if ( len > bestlen ) { - continue; - } - - if ( !trap_InPVS( origin, eloc->r.currentOrigin ) ) { - continue; - } - - bestlen = len; - best = eloc; - } - - return best; -} - -/* -=========== -G_Client_GetLocationMsg - -Report a location for the player. Uses placed nearby target_location entities -============ -*/ -qboolean G_Client_GetLocationMsg(gentity_t *ent, char *loc, int32_t loclen) -{ - gentity_t* best = NULL; - - best = G_Client_GetLocation( ent ); - - if (best == NULL) { - return qfalse; - } - - if (best->count != 0) { - if (best->count < 0) - best->count = 0; - if (best->count > 7) - best->count = 7; - Com_sprintf(loc, loclen, "%c%c%s" S_COLOR_WHITE, Q_COLOR_ESCAPE, best->count + '0', best->message ); - } else { - Com_sprintf(loc, loclen, "%s", best->message); - } - - return qtrue; -} - -/* -================== -G_Client_CheckHealthInfoMessage - -Sends Health Changes to proper clients - -Format: - clientNum health - -================== -*/ -static void G_Client_CheckHealthInfoMessage( void ) { - char entry[1024]; - char string[1400]; - int32_t stringlength = 0; - int32_t i = 0; - int32_t j = 0; - int32_t t = 0; - int32_t sendToCnt = 0; - int32_t cnt = 0; - int32_t sentCnt = 0; - int32_t h = 0; - int32_t clients[MAX_CLIENTS]; - int32_t sendToClients[MAX_CLIENTS]; - gentity_t* player = NULL; - gentity_t* ent = NULL; - - memset(entry, 0, sizeof(entry)); - memset(string, 0, sizeof(string)); - memset(clients, 0, sizeof(clients)); - memset(sendToClients, 0, sizeof(sendToClients)); - - //only send this to medics or spectators or adminz - for ( ; i < g_maxclients.integer; i++) { - - if ( (level.clients[i].pers.connected == CON_CONNECTED) && (level.clients[i].ps.stats[STAT_HEALTH] > 0) && //make sure they've actually spawned in already - ((level.clients[i].sess.sessionTeam == TEAM_SPECTATOR) || (g_classData[level.clients[i].sess.sessionClass].isMedical) || (g_classData[level.clients[i].sess.sessionClass].isAdmin) ) ) { - sendToClients[sendToCnt++] = i; - } - } - - if ( sendToCnt == 0 ) { - //no-one to send to - return; - } - - //only send those clients whose health has changed this cycle - //NB: there's a prob with client 0 in here.... - for (i = 0, cnt = 0; i < g_maxclients.integer; i++) { - player = g_entities + i; - if ( (player != NULL) && player->inuse && (player->old_health != player->health) && ( (player->health > 0) || (player->old_health > 0) )) { - clients[cnt++] = i; - player->old_health = player->health; - } - } - - if ( cnt == 0 ) { - //no-one relevant changed health - return; - } - - for ( t = 0; t < sendToCnt; t++ ) { - ent = g_entities + sendToClients[t]; - sentCnt = 0; - - // send the latest information on all clients - string[0] = 0; - stringlength = 0; - - for (i = 0; i < cnt; i++) { - player = g_entities + clients[i]; - - if ( ent == player ) {//don't send the ent his own health - continue; - } - - //send this one - sentCnt++; - - h = player->health; - if (h < 0) h = 0; - - Com_sprintf (entry, sizeof(entry), " %i %i", clients[i], h); - j = strlen(entry); - if (stringlength + j > sizeof(string)) - break; - strcpy (string + stringlength, entry); - stringlength += j; - } - - if ( sentCnt != 0 ) { - trap_SendServerCommand( sendToClients[t], va("hinfo %i%s", sentCnt, string) ); - } - } -} - -/** - * Send client location information. - * - * \param ent The client. - */ -static void G_Client_LocationsMessage( gentity_t *ent ) { - char entry[1024]; - char string[1400]; - int32_t stringlength = 0; - int32_t i = 0; - int32_t j = 0; - int32_t cnt = 0; - gentity_t* player = NULL; - - //don't bother sending during intermission? - if ( level.intermissiontime != 0 ) { - return; - } - - memset(entry, 0, sizeof(entry)); - memset(string, 0, sizeof(string)); - - // figure out what client should be on the display - // we are limited to 8, but we want to use the top eight players - // but in client order (so they don't keep changing position on the overlay) - for ( ; i < g_maxclients.integer && cnt < TEAM_MAXOVERLAY; i++) { - player = g_entities + level.sortedClients[i]; - if ((player != NULL) && (player->inuse) && (player->client->sess.sessionTeam == ent->client->sess.sessionTeam) ) { - // TODO huh? remove? - } - } - - // send the latest information on all clients - for (i = 0, cnt = 0; i < g_maxclients.integer && cnt < TEAM_MAXOVERLAY; i++) { - player = g_entities + i; - //RPG-X | Phenix | 05/03/2005 - if ((player != NULL) && player->inuse) { - //to counter for the fact we could pwn the server doing this, remove all superfluous data - - Com_sprintf (entry, sizeof(entry), " %i %i ", i, player->client->pers.teamState.location); - j = strlen(entry); - if (stringlength + j > sizeof(string)) { - break; - } - strcpy (string + stringlength, entry); - stringlength += j; - cnt++; - } - } - - trap_SendServerCommand( ent-g_entities, va("tinfo %i%s", cnt, string) ); -} - -//TiM - Modified to work with RPG-X -void G_Client_CheckClientStatus(void) { - gentity_t* loc = NULL; - gentity_t* ent = NULL; - - if (level.time - level.lastTeamLocationTime > TEAM_LOCATION_UPDATE_TIME) { - int32_t i = 0; - - level.lastTeamLocationTime = level.time; - - for ( ; i < g_maxclients.integer; i++) { - ent = g_entities + i; - if ((ent != NULL) && (ent->inuse)) { - loc = G_Client_GetLocation( ent ); - if (loc != NULL) { - ent->client->pers.teamState.location = loc->health; - } else { - ent->client->pers.teamState.location = 0; - } - } - } - - for (i = 0; i < g_maxclients.integer; i++) { - ent = g_entities + i; - if ((ent != NULL) && (ent->inuse)) { - G_Client_LocationsMessage( ent ); - } - } - - G_Client_CheckHealthInfoMessage(); - } -} - -/* -============ -G_Client_AddScore - -Adds score to both the client and his team -============ -*/ -void G_Client_AddScore( gentity_t *ent, int score ) { - if ( !ent ) - { - return; - } - if ( !ent->client ) { - return; - } - - if(!ent->client->UpdateScore) - { - return; - } - - ent->client->ps.persistant[PERS_SCORE] += score; - //don't add score to team score during elimination - if (g_gametype.integer == GT_TEAM) - {//this isn't capture score - level.teamScores[ ent->client->ps.persistant[PERS_TEAM] ] += score; - } - G_Client_CalculateRanks( qfalse ); - - //RPG-X: RedTechie - Lets enable score updating without this scores will not be updated - ent->client->UpdateScore = qfalse; -} - -/* -============= -SortRanks - -============= -*/ -static int QDECL SortRanks( const void *a, const void *b ) { - gclient_t *ca, *cb; - - ca = &level.clients[*(int *)a]; - cb = &level.clients[*(int *)b]; - - // sort special clients last - if ( ca->sess.spectatorState == SPECTATOR_SCOREBOARD || ca->sess.spectatorClient < 0 ) { - return 1; - } - if ( cb->sess.spectatorState == SPECTATOR_SCOREBOARD || cb->sess.spectatorClient < 0 ) { - return -1; - } - - // then connecting clients - if ( ca->pers.connected == CON_CONNECTING ) { - return 1; - } - if ( cb->pers.connected == CON_CONNECTING ) { - return -1; - } - - - // then spectators - if ( ca->sess.sessionTeam == TEAM_SPECTATOR && cb->sess.sessionTeam == TEAM_SPECTATOR ) { - if ( ca->sess.spectatorTime < cb->sess.spectatorTime ) { - return -1; - } - if ( ca->sess.spectatorTime > cb->sess.spectatorTime ) { - return 1; - } - return 0; - } - if ( ca->sess.sessionTeam == TEAM_SPECTATOR ) { - return 1; - } - if ( cb->sess.sessionTeam == TEAM_SPECTATOR ) { - return -1; - } - - // then sort by score & number of times killed - if ( ca->ps.persistant[PERS_SCORE] - > cb->ps.persistant[PERS_SCORE] ) { - return -1; - } - if ((ca->ps.persistant[PERS_SCORE] == cb->ps.persistant[PERS_SCORE]) && - (ca->ps.persistant[PERS_KILLED] < cb->ps.persistant[PERS_KILLED]) ) - { return -1;} - - if ( ca->ps.persistant[PERS_SCORE] - < cb->ps.persistant[PERS_SCORE] ) { - return 1; - } - if ((ca->ps.persistant[PERS_SCORE] == cb->ps.persistant[PERS_SCORE]) && - (ca->ps.persistant[PERS_KILLED] > cb->ps.persistant[PERS_KILLED]) ) - { return 1;} - - return 0; -} - -/* -============ -G_Client_CalculateRanks - -Recalculates the score ranks of all players -This will be called on every client connect, begin, disconnect, death, -and team change. - -FIXME: for elimination, the last man standing must be ranked first -============ -*/ -void G_Client_CalculateRanks( qboolean fromExit ) { - int32_t i = 0; - - gclient_t *cl; - - level.follow1 = -1; - level.follow2 = -1; - level.numConnectedClients = 0; - level.numNonSpectatorClients = 0; - level.numPlayingClients = 0; - level.numVotingClients = 0; // don't count bots - for ( ; i < level.maxclients ; i++ ) { - if ( level.clients[i].pers.connected != CON_DISCONNECTED ) { - level.sortedClients[level.numConnectedClients] = i; - level.numConnectedClients++; - - if ( level.clients[i].sess.sessionTeam != TEAM_SPECTATOR ) { - level.numNonSpectatorClients++; - - // decide if this should be auto-followed - if ( level.clients[i].pers.connected == CON_CONNECTED ) { - level.numPlayingClients++; - if ( (g_entities[i].r.svFlags & SVF_BOT) == 0 ) { - level.numVotingClients++; - } - if ( level.follow1 == -1 ) { - level.follow1 = i; - } else if ( level.follow2 == -1 ) { - level.follow2 = i; - } - } - } - } - } - - qsort( level.sortedClients, (size_t)level.numConnectedClients, - sizeof(level.sortedClients[0]), SortRanks ); - - // set the rank value for all clients that are connected and not spectators - if ( g_gametype.integer >= GT_TEAM ) { - // in team games, rank is just the order of the teams, 0=red, 1=blue, 2=tied - for ( i = 0; i < level.numConnectedClients; i++ ) { - cl = &level.clients[ level.sortedClients[i] ]; - if ( level.teamScores[TEAM_RED] == level.teamScores[TEAM_BLUE] ) { - cl->ps.persistant[PERS_RANK] = 2; - } else if ( level.teamScores[TEAM_RED] > level.teamScores[TEAM_BLUE] ) { - cl->ps.persistant[PERS_RANK] = 0; - } else { - cl->ps.persistant[PERS_RANK] = 1; - } - } - } else { - int32_t rank = -1; - int32_t score = 0; - for ( i = 0; i < level.numPlayingClients; i++ ) { - int32_t newScore = 0; - - cl = &level.clients[ level.sortedClients[i] ]; - newScore = cl->ps.persistant[PERS_SCORE]; - if ( i == 0 || newScore != score ) { - rank = i; - // assume we aren't tied until the next client is checked - level.clients[ level.sortedClients[i] ].ps.persistant[PERS_RANK] = rank; - } else { - // we are tied with the previous client - level.clients[ level.sortedClients[i-1] ].ps.persistant[PERS_RANK] = rank | RANK_TIED_FLAG; - level.clients[ level.sortedClients[i] ].ps.persistant[PERS_RANK] = rank | RANK_TIED_FLAG; - } - score = newScore; - if ( g_gametype.integer == GT_SINGLE_PLAYER && level.numPlayingClients == 1 ) { - level.clients[ level.sortedClients[i] ].ps.persistant[PERS_RANK] = rank | RANK_TIED_FLAG; - } - } - } - - // set the CS_SCORES1/2 configstrings, which will be visible to everyone - if ( g_gametype.integer >= GT_TEAM ) { - trap_SetConfigstring( CS_SCORES1, va("%i", level.teamScores[TEAM_RED] ) ); - trap_SetConfigstring( CS_SCORES2, va("%i", level.teamScores[TEAM_BLUE] ) ); - } else { - if ( level.numConnectedClients == 0 ) { - trap_SetConfigstring( CS_SCORES1, va("%i", SCORE_NOT_PRESENT) ); - trap_SetConfigstring( CS_SCORES2, va("%i", SCORE_NOT_PRESENT) ); - } else if ( level.numConnectedClients == 1 ) { - trap_SetConfigstring( CS_SCORES1, va("%i", level.clients[ level.sortedClients[0] ].ps.persistant[PERS_SCORE] ) ); - trap_SetConfigstring( CS_SCORES2, va("%i", SCORE_NOT_PRESENT) ); - } else { - trap_SetConfigstring( CS_SCORES1, va("%i", level.clients[ level.sortedClients[0] ].ps.persistant[PERS_SCORE] ) ); - trap_SetConfigstring( CS_SCORES2, va("%i", level.clients[ level.sortedClients[1] ].ps.persistant[PERS_SCORE] ) ); - } - } - - // if we are at the intermission, send the new info to everyone - if ( level.intermissiontime != 0 ) { - SendScoreboardMessageToAllClients(); - } -} - -void G_Client_UpdateSoundZones(void) { - int32_t i = 0; - int32_t b = 0; - int32_t entlist[MAX_GENTITIES]; - int32_t zones[MAX_CLIENTS]; - int32_t count = 0; - char supdate[MAX_STRING_CHARS]; - - memset(&zones, 0, sizeof(zones)); - memset(&supdate, 0, sizeof(supdate)); - - for( ; i < MAX_GENTITIES; i++) { - if((g_entities[i].type == ENT_TARGET_ZONE) && (g_entities[i].count == 3)) { - memset(&entlist, 0, sizeof(entlist)); - count = trap_EntitiesInBox(g_entities[i].r.mins, g_entities[i].r.maxs, &entlist, MAX_GENTITIES); - - for(b = 0; b < count; b++) { - if(g_entities[entlist[b]].client != NULL) { - zones[g_entities[entlist[b]].client->ps.clientNum] = g_entities[i].s.number; - } - } - } - } - - for(i = 0; i < MAX_CLIENTS; i++) { - if(strlen(supdate) != 0) { - Com_sprintf(supdate, sizeof(supdate), "%s\\c%d\\%d", supdate, i, zones[i]); - } else { - Com_sprintf(supdate, sizeof(supdate), "c%d\\%d", i, zones[i]); - } - } - - for(i = 0; i < MAX_GENTITIES; i++) { - if(g_entities[i].client != NULL) { - trap_SendServerCommand(i, va("slup %s", supdate)); - } - } -} - -void G_Client_SetScore(gentity_t* ent, int32_t score) { - - G_Assert(ent, (void)0); - G_Assert(ent->client, (void)0); - - if (!ent->client->UpdateScore) { - return; - } - - ent->client->ps.persistant[PERS_SCORE] = score; - G_Client_CalculateRanks(qfalse); - - // TiM: send the current scoring to all clients - SendScoreboardMessageToAllClients(); - - //RPG-X: RedTechie - Lets enable score updating without this scores will not be updated - ent->client->UpdateScore = qfalse; -} - -void G_Client_TossClientItems(gentity_t* self, qboolean dis_con) { - double angle = 0; - int32_t i = 0; - int32_t times = 0; - gentity_t* drop = NULL; - playerState_t *ps = NULL; - gitem_t* item = NULL; - - G_Assert(self, (void)0); - G_Assert(self->client, (void)0); - - ps = &self->client->ps; - - if ((self->flags & FL_CLOAK) != 0) { - // remove the invisible powerup if the player is cloaked. - //RPG-X: RedTechie - Also remove ghost - ps->powerups[PW_GHOST] = level.time; - ps->powerups[PW_INVIS] = level.time; - } - - if ((self->flags & FL_FLY) != 0) { - // remove the flying powerup if the player is flying. - ps->powerups[PW_FLIGHT] = level.time; - } - - //RPG-X | Phenix | 8/8/2004 - if ((self->flags & FL_EVOSUIT) != 0) { - // remove the evosuit powerup - ps->powerups[PW_EVOSUIT] = level.time; - } - - // drop all the powerups if not in teamplay - if (g_gametype.integer != GT_TEAM) { - angle = 45; - for (i = 1; i < PW_NUM_POWERUPS; i++) { - if (ps->powerups[i] > level.time) { - item = BG_FindItemForPowerup(i); - if (item == NULL) { - continue; - } - - drop = Drop_Item(self, item, angle); - // decide how many seconds it has left - drop->count = (ps->powerups[i] - level.time) / 1000; - if (drop->count < 1) { - drop->count = 1; - } - angle += 45; - } - } - } - - // RPG-X | Marcin | 30/12/2008 - // ... - if (rpg_allowWeaponDrop.integer == 0 || rpg_dropOnDeath.integer == 0 || dis_con) { - return; - } - - // Drop ALL weapons in inventory - for (i = 0; i < WP_NUM_WEAPONS; ++i) { - // these weapons should not be tossed (hand and null) - if (Max_Weapons[i] == NULL) { - continue; - } - - //RPG-X | GSIO01 | 08/05/2009: let's make sure we only drop weapons the player has - item = NULL; - if (ps->ammo[i]) { - times = ps->ammo[i]; - item = BG_FindItemForWeapon((weapon_t)i); - while (times-- > 0) { // the 'goes towards' operator :p - Drop_Item(self, item, 0); - } - } - } - - // then remove weapons - - for (i = 0; i < WP_NUM_WEAPONS; ++i) { - ps->stats[STAT_WEAPONS] &= ~i; - ps->ammo[i] = 0; - } -} - -void body_die(gentity_t* self, gentity_t* inflictor, gentity_t* attacker, int32_t damage, int32_t meansOfDeath) { - int32_t contents = 0; - - G_Assert(self, (void)0); - - contents = trap_PointContents(self->r.currentOrigin, -1); - if (rpg_medicsrevive.integer == 1 && !(contents & CONTENTS_NODROP) && (meansOfDeath != MOD_TRIGGER_HURT)) { - if (self->health > GIB_HEALTH_IMPOSSIBLE) { - return; - } - - } else { - if (self->health > GIB_HEALTH) { - return; - } - } - G_Combat_GibEntity(self, 0); -} - -char* G_Client_ClassNameForValue(pclass_t pClass) { - static char buffer[MAX_QPATH]; - char *ptr = NULL; - - trap_Cvar_VariableStringBuffer(va("rpg_%sPass", g_classData[pClass].consoleName), buffer, sizeof(buffer)); - - ptr = buffer; - - return ptr; -} - -// these are just for logging, the client prints its own messages -char* modNames[MOD_MAX] = { - "MOD_UNKNOWN", - - "MOD_WATER", - "MOD_SLIME", - "MOD_LAVA", - "MOD_CRUSH", - "MOD_TELEFRAG", - "MOD_FALLING", - "MOD_SUICIDE", - "MOD_TARGET_LASER", - "MOD_TRIGGER_HURT", - - // Trek weapons - "MOD_PHASER", - "MOD_PHASER_ALT", - "MOD_CRIFLE", - "MOD_CRIFLE_SPLASH", - "MOD_CRIFLE_ALT", - "MOD_CRIFLE_ALT_SPLASH", - "MOD_IMOD", - "MOD_IMOD_ALT", - "MOD_SCAVENGER", - "MOD_SCAVENGER_ALT", - "MOD_SCAVENGER_ALT_SPLASH", - "MOD_STASIS", - "MOD_STASIS_ALT", - "MOD_GRENADE", - "MOD_GRENADE_ALT", - "MOD_GRENADE_SPLASH", - "MOD_GRENADE_ALT_SPLASH", - "MOD_TETRYON", - "MOD_TETRYON_ALT", - "MOD_DREADNOUGHT", - "MOD_DREADNOUGHT_ALT", - "MOD_QUANTUM", - "MOD_QUANTUM_SPLASH", - "MOD_QUANTUM_ALT", - "MOD_QUANTUM_ALT_SPLASH", - - "MOD_DETPACK", - "MOD_SEEKER" - - //expansion pack - "MOD_KNOCKOUT", - "MOD_ASSIMILATE", - "MOD_BORG", - "MOD_BORG_ALT", - - "MOD_RESPAWN", - "MOD_EXPLOSION", -}; //must be kept up to date with bg_public, meansOfDeath_t - -/* -================== -G_Client_Die -Heavly Modifyed By: RedTechie -RPG-X: Marcin: a little bit modified - 30/12/2008 -================== -*/ -void G_Client_Die(gentity_t* self, gentity_t* inflictor, gentity_t* attacker, int32_t damage, int32_t meansOfDeath) { - //--------------------- - //RPG-X: RedTechie - Check to see if medics revive people and not respawn if true use my fake death insead :) - //--------------------- - int32_t contents = 0; - - G_Assert(self, (void)0); - G_Assert(self->client, (void)0); - - //RPG-X: RedTechie - Make sure there not getting killed by a trigger kill or the medics wont be able to heal them - contents = trap_PointContents(self->r.currentOrigin, -1); - - if (rpg_medicsrevive.integer == 1 && !(contents & CONTENTS_NODROP) && (meansOfDeath != MOD_TRIGGER_HURT)) { - char* classname = NULL; - char* killerName = NULL; - char* obit = NULL; - int32_t anim = 0; - int32_t killer = 0; - int32_t i = 0; - gentity_t* detpack = NULL; - gentity_t* ent = NULL; - playerState_t* ps = &self->client->ps; - - //RPG-X: RedTechie - Blow up a detpack if some one placed it and died - classname = BG_FindClassnameForHoldable(HI_DETPACK); - if (classname != NULL) { - while ((detpack = G_Find(detpack, FOFS(classname), classname)) != NULL) { - if (detpack->parent == self) { - detpack->think = DetonateDetpack; // Detonate next think. - detpack->nextthink = level.time; - } - } - } - - - //RPG-X: Redtechie - Do some score keeping witch we commented out and log - if (attacker != NULL) { - killer = attacker->s.number; - if (attacker->client != NULL) { - killerName = attacker->client->pers.netname; - } else { - killerName = ""; - } - } else { - killer = ENTITYNUM_WORLD; - killerName = ""; - } - - if (killer < 0 || killer >= MAX_CLIENTS) { - killer = ENTITYNUM_WORLD; - killerName = ""; - } - - if (meansOfDeath < 0 || meansOfDeath >= sizeof(modNames) / sizeof(modNames[0])) { - obit = ""; - } else { - obit = modNames[meansOfDeath]; - } - - G_LogPrintf("Kill: %i %i %i: %s killed %s by %s\n", killer, self->s.number, meansOfDeath, killerName, self->client->pers.netname, obit); - - G_LogWeaponKill(killer, meansOfDeath); - G_LogWeaponDeath(self->s.number, self->s.weapon); - if (attacker != NULL && attacker->client != NULL && attacker->inuse) { - G_LogWeaponFrag(killer, self->s.number); - } - - if (meansOfDeath != MOD_RESPAWN && meansOfDeath != MOD_CUSTOM_DIE) { - // broadcast the death event to everyone - ent = G_TempEntity(self->r.currentOrigin, EV_OBITUARY); - ent->s.eventParm = meansOfDeath; - ent->s.otherEntityNum = self->s.number; - ent->s.otherEntityNum2 = killer; - ent->r.svFlags = SVF_BROADCAST; // send to everyone - } - - self->enemy = attacker; - - ps->persistant[PERS_KILLED]++; - if (self == attacker) { - self->client->pers.teamState.suicides++; - } else { - //RPG-X | Phenix | 06/04/2005 - // N00b Protection, you kill two people and puff your auto n00b! - - if (attacker != NULL) { - if (attacker->client != NULL) { - if (G_Client_IsAdmin(attacker) == qfalse) { - attacker->n00bCount++; - - attacker->client->fraggerTime = level.time + (rpg_fraggerSpawnDelay.integer * 1000); - - if (rpg_kickAfterXkills.integer < 1) { - trap_SendServerCommand(attacker - g_entities, va("print \"^7Server: You have been caught n00bing, you have been temporary put in the n00b class.\n\"")); - } else { - trap_SendServerCommand(attacker - g_entities, va("print \"^7Server: You have been caught n00bing, %i more times and you will be kicked.\n\"", (rpg_kickAfterXkills.integer - attacker->n00bCount))); - } - - if ((attacker->n00bCount >= rpg_kickAfterXkills.integer) && (rpg_kickAfterXkills.integer != 0)) { - trap_DropClient(attacker->s.number, "Kicked: Do Not N00b!"); - } else { - for (i = 0; g_classData[i].consoleName[0] && i < MAX_CLASSES; i++) { - if (g_classData[i].isn00b) { - char conName[64]; - trap_Cvar_VariableStringBuffer(va("rpg_%sPass", conName), conName, sizeof(conName)); - - Q_strncpyz(attacker->client->origClass, G_Client_ClassNameForValue(attacker->client->sess.sessionClass), sizeof(attacker->client->origClass)); - attacker->client->n00bTime = level.time + 10000; - SetClass(attacker, conName, NULL, qfalse); - break; - } - } - } - } - } - } - } - - //RPG-X: RedTechie no noclip - if (self->client->noclip) { - self->client->noclip = qfalse; - } - - //RPG-X: RedTechie - Toss items - //RPG-X: Marcin - not when respawning - 30/12/2008 - if (meansOfDeath != MOD_RESPAWN) { - G_Client_TossClientItems(self, qfalse); - } - - ps->pm_type = PM_DEAD; - - self->takedamage = qfalse; - - ps->weapon = WP_0; - ps->weaponstate = WEAPON_READY; - self->r.contents = CONTENTS_CORPSE; - - //-TiM - - self->s.loopSound = 0; - - self->r.maxs[2] = -8; - - //RPG-X: RedTechie - Wait....forever - self->client->respawnTime = level.time + 1000000000; - - //Play death sound - //RPG-X: RedTechie - No pain sound when they change class - if (meansOfDeath != MOD_RESPAWN) { - G_AddEvent(self, irandom(EV_DEATH1, EV_DEATH3), killer); - //if we died from falling, add a nice "splat' sound lol - if (meansOfDeath == MOD_FALLING) { - G_AddEvent(self, EV_SPLAT, killer); - } - } - - //RPG-X : Model system - Death animations now based on vector hit - if (meansOfDeath == MOD_FALLING) { - anim = BOTH_FALLDEATH1LAND; - } else if (self->waterlevel == 3) { - anim = BOTH_FLOAT2; - } else { - if (meansOfDeath == MOD_PHASER || meansOfDeath == MOD_PHASER_ALT) { - if ((self->client->lasthurt_location & LOCATION_FRONT) != 0) { - anim = BOTH_DEATHBACKWARD1; - } else if ((self->client->lasthurt_location & LOCATION_BACK) != 0) { - anim = BOTH_DEATHFORWARD2; - } else if ((self->client->lasthurt_location & LOCATION_LEFT) != 0) { - anim = BOTH_DEATH2; - } else if ((self->client->lasthurt_location & LOCATION_RIGHT) != 0) { - anim = BOTH_DEATH2; - } else { - anim = BOTH_DEATH1; - } - } else { - if ((self->client->lasthurt_location & LOCATION_FRONT) != 0) { - anim = BOTH_DEATHBACKWARD2; - } else if ((self->client->lasthurt_location & LOCATION_BACK) != 0) { - anim = BOTH_DEATHFORWARD1; - } else if ((self->client->lasthurt_location & LOCATION_LEFT) != 0) { - anim = BOTH_DEATHFORWARD2; - } else if ((self->client->lasthurt_location & LOCATION_RIGHT) != 0) { - anim = BOTH_DEATHFORWARD2; - } else { - anim = BOTH_DEATH1; - } - } - } - - //TiM - ps->stats[LEGSANIM] = ((ps->stats[LEGSANIM] & ANIM_TOGGLEBIT) ^ ANIM_TOGGLEBIT) | anim; - ps->stats[TORSOANIM] = ((ps->stats[TORSOANIM] & ANIM_TOGGLEBIT) ^ ANIM_TOGGLEBIT) | anim; - - trap_LinkEntity(self); - - BG_PlayerStateToEntityState(&self->client->ps, &self->s, qtrue); - - G_Client_UserinfoChanged(self->s.clientNum); - - ClientEndFrame(self); - - G_Client_StoreClientInitialStatus(self); - //--------------------- - //RPG-X: RedTechie - If it dose equal 0 use regular die - //--------------------- - } else { - char* killerName = NULL; - char* obit = NULL; - char* classname = NULL; - int anim = 0; - int killer = 0; - int i = 0; - int BottomlessPitDeath = 0; - static int deathNum; - gentity_t* ent = NULL; - gentity_t* detpack = NULL; - playerState_t* ps = &self->client->ps; - - if (ps->pm_type == PM_DEAD) { - return; - } - - if (level.intermissiontime != 0) { - return; - } - - //RPG-X: RedTechie - Trying to make sure player dies when there health is 1 without medics revive turned on - //RPG-X | Phenix | 05/04/2005 - Read learn that "=" sets where "==" is an if statement!!! - if (self->health == 1) { - self->health = 0; - } - - ps->pm_type = PM_DEAD; - //need to copy health here because pm_type was getting reset to PM_NORMAL if ClientThink_real was called before the STAT_HEALTH was updated - ps->stats[STAT_HEALTH] = self->health; - - // check if we are in a NODROP Zone and died from a TRIGGER HURT - // if so, we assume that this resulted from a fall to a "bottomless pit" and - // treat it differently... - // - // Any problems with other places in the code? - // - BottomlessPitDeath = 0; // initialize - - contents = trap_PointContents(self->r.currentOrigin, -1); - if ((contents & CONTENTS_NODROP) != 0 && (meansOfDeath == MOD_TRIGGER_HURT)) { - BottomlessPitDeath = 1; - } - - // similarly, if El Corpso here has already dropped a detpack, blow it up - classname = BG_FindClassnameForHoldable(HI_DETPACK); - if (classname) { - while ((detpack = G_Find(detpack, FOFS(classname), classname)) != NULL) { - if (detpack->parent == self) { - detpack->think = DetonateDetpack; // Detonate next think. - detpack->nextthink = level.time; - } - } - } - - if (attacker != NULL) { - killer = attacker->s.number; - if (attacker->client != NULL) { - killerName = attacker->client->pers.netname; - } else { - killerName = ""; - } - } else { - killer = ENTITYNUM_WORLD; - killerName = ""; - } - - if (killer < 0 || killer >= MAX_CLIENTS) { - killer = ENTITYNUM_WORLD; - killerName = ""; - } - - if (meansOfDeath < 0 || meansOfDeath >= sizeof(modNames) / sizeof(modNames[0])) { - obit = ""; - } else { - obit = modNames[meansOfDeath]; - } - - G_LogPrintf("Kill: %i %i %i: %s killed %s by %s\n", killer, self->s.number, meansOfDeath, killerName, self->client->pers.netname, obit); - - G_LogWeaponKill(killer, meansOfDeath); - G_LogWeaponDeath(self->s.number, self->s.weapon); - if (attacker != NULL && attacker->client != NULL && attacker->inuse) { - G_LogWeaponFrag(killer, self->s.number); - } - - if (meansOfDeath != MOD_RESPAWN) { - // broadcast the death event to everyone - ent = G_TempEntity(self->r.currentOrigin, EV_OBITUARY); - ent->s.eventParm = meansOfDeath; - ent->s.otherEntityNum = self->s.number; - ent->s.otherEntityNum2 = killer; - ent->r.svFlags = SVF_BROADCAST; // send to everyone - } - - self->enemy = attacker; - - ps->persistant[PERS_KILLED]++; - if (self == attacker) { - self->client->pers.teamState.suicides++; - } - - //RPG-X: Redtechie - No awards or score calculations - //////////////////////////////////////////////////////////////////////// - if (attacker != NULL && attacker->client != NULL) { - if (attacker == self) { - if (meansOfDeath != MOD_RESPAWN) {//just changing class - G_Client_AddScore(attacker, -1); - } - } else { - attacker->client->pers.teamState.frags++; - G_Client_AddScore(attacker, 1); - - // Check to see if the player is on a streak. - attacker->client->streakCount++; - - attacker->client->lastKillTime = level.time; - } - } else { - if (meansOfDeath != MOD_RESPAWN) {//not just changing class - G_Client_AddScore(self, -1); - } - } - //////////////////////////////////////////////////////////////////////// - - //RPG-X: Redtechie - agian no need - // Add team bonuses - //Team_FragBonuses(self, inflictor, attacker); - - // if client is in a nodrop area, don't drop anything (but return CTF flags!) - if ((contents & CONTENTS_NODROP) == 0 && meansOfDeath != MOD_SUICIDE && meansOfDeath != MOD_RESPAWN) {//action hero doesn't drop stuff - //don't drop stuff in specialty mode - if (meansOfDeath != MOD_RESPAWN) { - G_Client_TossClientItems(self, qfalse); - } - } - - DeathmatchScoreboardMessage(self); // show scores - // send updated scores to any clients that are following this one, - // or they would get stale scoreboards - for (i = 0; i < level.maxclients; i++) { - gclient_t* client = NULL; - - client = &level.clients[i]; - if (client == NULL) { - continue; - } - - if (client->pers.connected != CON_CONNECTED) { - continue; - } - if (client->sess.sessionTeam != TEAM_SPECTATOR) { - continue; - } - if (client->sess.spectatorClient == self->s.number) { - DeathmatchScoreboardMessage(g_entities + i); - } - } - - self->takedamage = qtrue; // can still be gibbed - - self->s.weapon = WP_0; - self->s.powerups = 0; - self->r.contents = CONTENTS_CORPSE; - - self->s.loopSound = 0; - - self->r.maxs[2] = -8; - - // don't allow respawn until the death anim is done - // g_forcerespawn may force spawning at some later time - self->client->respawnTime = level.time + 1700; - - // We always want to see the body for special animations, so make sure not to gib right away: - if (meansOfDeath == MOD_CRIFLE_ALT || - meansOfDeath == MOD_DETPACK || - meansOfDeath == MOD_QUANTUM_ALT || - meansOfDeath == MOD_DREADNOUGHT_ALT || - meansOfDeath == MOD_PHASER_ALT)//RPG-X: RedTechie - Added phaser alt disnt - { - self->health = 0; - } - - //RPG-X : Model system - Death animations now based on vector hit - if (meansOfDeath == MOD_FALLING) { - anim = BOTH_FALLDEATH1LAND; - } else if (self->waterlevel == 3) { - anim = BOTH_FLOAT2; - } else { - if (meansOfDeath == MOD_PHASER || meansOfDeath == MOD_PHASER_ALT) { - if ((self->client->lasthurt_location & LOCATION_FRONT) != 0) { - anim = BOTH_DEATHBACKWARD1; - } else if ((self->client->lasthurt_location & LOCATION_BACK) != 0) { - anim = BOTH_DEATHFORWARD2; - } else if ((self->client->lasthurt_location & LOCATION_LEFT) != 0) { - anim = BOTH_DEATH2; - } else if ((self->client->lasthurt_location & LOCATION_RIGHT) != 0) { - anim = BOTH_DEATH2; - } else { - anim = BOTH_DEATH1; - } - } else { - if ((self->client->lasthurt_location & LOCATION_FRONT) != 0) { - anim = BOTH_DEATHBACKWARD2; - } else if ((self->client->lasthurt_location & LOCATION_BACK) != 0) { - anim = BOTH_DEATHFORWARD1; - } else if ((self->client->lasthurt_location & LOCATION_LEFT) != 0) { - anim = BOTH_DEATHFORWARD2; - } else if ((self->client->lasthurt_location & LOCATION_RIGHT) != 0) { - anim = BOTH_DEATHFORWARD2; - } else { - anim = BOTH_DEATH1; - } - } - } - - ps->stats[LEGSANIM] = ((ps->stats[LEGSANIM] & ANIM_TOGGLEBIT) ^ ANIM_TOGGLEBIT) | anim; - ps->stats[TORSOANIM] = ((ps->stats[TORSOANIM] & ANIM_TOGGLEBIT) ^ ANIM_TOGGLEBIT) | anim; - - if ((BottomlessPitDeath == 1) && (killer == ENTITYNUM_WORLD)) { - //G_AddEvent( self, EV_FALL_FAR, killer ); ?? Need to play falling SF now, or - // use designer trigger?? - //FIXME: need *some* kind of death anim! - } else { - // normal death - - switch (meansOfDeath) { - case MOD_PHASER_ALT: //RPG-X: RedTechie - Added better effect for alt phaser - if (rpg_phaserdisintegrates.integer == 1) {//RPG-X: RedTechie - Check to see if we want this - G_AddEvent(self, EV_DISINTEGRATION, killer); - ps->powerups[PW_DISINTEGRATE] = level.time + 100000; - VectorClear(ps->velocity); - self->takedamage = qfalse; - self->r.contents = 0; - } - break; - case MOD_CRIFLE_ALT: - break; - case MOD_QUANTUM_ALT: - G_AddEvent(self, EV_DISINTEGRATION2, killer); - ps->powerups[PW_EXPLODE] = level.time + 100000; - VectorClear(ps->velocity); - self->takedamage = qfalse; - self->r.contents = 0; - break; - case MOD_SCAVENGER_ALT: - case MOD_SCAVENGER_ALT_SPLASH: - case MOD_GRENADE: - case MOD_GRENADE_ALT: - case MOD_GRENADE_SPLASH: - case MOD_GRENADE_ALT_SPLASH: - case MOD_QUANTUM: - case MOD_QUANTUM_SPLASH: - case MOD_QUANTUM_ALT_SPLASH: - case MOD_DETPACK: - G_AddEvent(self, EV_EXPLODESHELL, killer); - ps->powerups[PW_EXPLODE] = level.time + 100000; - VectorClear(ps->velocity); - self->takedamage = qfalse; - self->r.contents = 0; - break; - case MOD_DREADNOUGHT: - case MOD_DREADNOUGHT_ALT: - G_AddEvent(self, EV_ARCWELD_DISINT, killer); - ps->powerups[PW_ARCWELD_DISINT] = level.time + 100000; - VectorClear(ps->velocity); - self->takedamage = qfalse; - self->r.contents = 0; - break; - case MOD_FALLING: - G_AddEvent(self, EV_SPLAT, killer); - break; - default: - G_AddEvent(self, irandom(EV_DEATH1, EV_DEATH3), killer); - break; - } - - // the body can still be gibbed - self->die = body_die; - - } - // globally cycle through the different death animations - deathNum = (deathNum + 1) % 3; - - trap_LinkEntity(self); - }//RPG-X: RedTechie - End of my if statment for medics revive check -}//RPG-X: RedTechie - End of void - -qboolean G_Client_IsBorg(gentity_t* ent) { - if (ent == NULL) { - return qfalse; - } - - if (ent->client == NULL) { - return qfalse; - } - - if (g_classData[ent->client->sess.sessionClass].isBorg) { - return qtrue; - } else { - return qfalse; - } -} - - - +// Copyright (C) 1999-2000 Id Software, Inc. +// +#include "g_client.h" +#include "g_local.h" +#include "g_groups.h" +#include "g_main.h" +#include "g_cmds.h" +#include "g_main.h" +#include "g_spawn.h" +#include "g_items.h" +#include "g_lua.h" +#include "g_logger.h" + +reconData_t g_reconData[MAX_RECON_NAMES]; //!< recon data for a limited ammount of clients +int32_t g_reconNum; + +// g_client.c -- client functions that don't happen every frame + +void G_Client_StoreClientInitialStatus( gentity_t *ent ); + +//! players mins +static vec3_t playerMins = {-12, -12, -24}; //RPG-X : TiM - {-15, -15, -24} +//! players maxs +static vec3_t playerMaxs = {12, 12, 32}; // {15, 15, 32} + +clInitStatus_t clientInitialStatus[MAX_CLIENTS]; + +//TiM: For easier transport setup +/** +* Function that makes transport setup easier +* \author Ubergames - TiM +*/ +void G_InitTransport( int32_t clientNum, vec3_t origin, vec3_t angles ) { + gentity_t* tent = NULL; + + TransDat[clientNum].beamTime = level.time + 8000; + g_entities[clientNum].client->ps.powerups[PW_BEAM_OUT] = level.time + 8000; + + //Transfer stored data to active beamer + VectorCopy( origin, TransDat[clientNum].currentCoord.origin ); + VectorCopy( angles, TransDat[clientNum].currentCoord.angles ); + + tent = G_TempEntity( g_entities[clientNum].client->ps.origin, EV_PLAYER_TRANSPORT_OUT ); + tent->s.clientNum = clientNum; +} + +/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) INITIAL +-----DESCRIPTION----- +potential spawning position for deathmatch games. + +-----SPAWNFLAGS----- +1: INITIAL - Preferred spawn for the first spawn of a client when entering a match. + +-----KEYS----- +"target" - entities with matching targetname will be fired if someone spawns here. +"nobots" - if 1 will prevent bots from using this spot. +"nohumans" - if 1 will prevent non-bots from using this spot. +*/ +/** +* Spawn function for deathmatch spawnpoint +*/ +void SP_info_player_deathmatch( gentity_t *ent ) { + int32_t i = 0; + + ent->type = ENT_INFO_PLAYER_START; + + if(strcmp(ent->classname, "info_player_deathmatch") != 0) { + ent->classname = "info_player_deathmatch"; + } + + G_SpawnInt( "nobots", "0", &i); + if ( i != 0 ) { + ent->flags |= FL_NO_BOTS; + } + G_SpawnInt( "nohumans", "0", &i ); + if ( i != 0 ) { + ent->flags |= FL_NO_HUMANS; + } + + trap_LinkEntity(ent); +} + +/*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32) +-----DESCRIPTION----- +The intermission will be viewed from this point. +It is also used to spawn spectators. +Target an info_notnull or similar for the view direction. + +-----SPAWNFLAGS----- +none + +-----KEYS----- +none +*/ +/** +* Spawn function for intermission entity. +*/ +void SP_info_player_intermission( gentity_t *ent ) { + ent->type = ENT_INFO_PLAYER_INTERMISSION; +} + +/** + * Determine whether spot would telefrag. + * + * \param spot Spot to check. + * \return Whether this spot would telefrag. + */ +static qboolean G_Client_SpotWouldTelefrag( gentity_t *spot ) { + int32_t i = 0; + int32_t num = 0; + int touch[MAX_GENTITIES]; + gentity_t* hit = NULL; + vec3_t mins; + vec3_t maxs; + + memset(touch, 0, sizeof(touch)); + + VectorAdd( spot->s.origin, playerMins, mins ); + VectorAdd( spot->s.origin, playerMaxs, maxs ); + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + for ( ; iclient && hit->client->ps.stats[STAT_HEALTH] > 0 ) { + return qtrue; + } + if (hit && hit->s.eType == ET_USEABLE && hit->s.modelindex == HI_SHIELD) { //hit a portable force field + return qtrue; + } + + + } + + return qfalse; +} + +/* +================ +SelectNearestDeathmatchSpawnPoint + +Find the spot that we DON'T want to use +================ +*/ +#define MAX_SPAWN_POINTS 256 +/** +* Find the spot that we DON'T want to use +*/ +static gentity_t* SelectNearestDeathmatchSpawnPoint( vec3_t from ) { + gentity_t* spot = NULL; + vec3_t delta; + double dist = 0.0; + double nearestDist = 999999.0; + gentity_t* nearestSpot = NULL; + + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { + + VectorSubtract( spot->s.origin, from, delta ); + dist = VectorLength( delta ); + if ( dist < nearestDist ) { + nearestDist = dist; + nearestSpot = spot; + } + } + + return nearestSpot; +} + + +/* +================ +SelectRandomDeathmatchSpawnPoint + +go to a random point that doesn't telefrag +================ +*/ +#define MAX_SPAWN_POINTS 256 +/** +* go to a random point that doesn't telefrag +*/ +static gentity_t* SelectRandomDeathmatchSpawnPoint( void ) { + gentity_t* spot = NULL; + int32_t count = 0; + int32_t selection = 0; + gentity_t* spots[MAX_SPAWN_POINTS]; + + memset(spots, 0, sizeof(spots)); + + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { + if ( G_Client_SpotWouldTelefrag( spot ) ) { + continue; + } + spots[ count ] = spot; + count++; + } + + if ( count == 0 ) { // no spots that won't telefrag + return G_Find( NULL, FOFS(classname), "info_player_deathmatch"); + } + + selection = rand() % count; + return spots[ selection ]; +} + + +gentity_t* G_Client_SelectSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) { + gentity_t* spot = NULL; + gentity_t* nearestSpot = SelectNearestDeathmatchSpawnPoint( avoidPoint ); + + spot = SelectRandomDeathmatchSpawnPoint ( ); + if ( spot == nearestSpot ) { + // roll again if it would be real close to point of death + spot = SelectRandomDeathmatchSpawnPoint ( ); + if ( spot == nearestSpot ) { + // last try + spot = SelectRandomDeathmatchSpawnPoint ( ); + } + } + + // find a single player start spot + if (spot == NULL) { + G_Error( "Couldn't find a spawn point" ); + return spot; + } + + VectorCopy (spot->s.origin, origin); + origin[2] += 9; + VectorCopy (spot->s.angles, angles); + + return spot; +} + +/* +=========== +SelectInitialSpawnPoint + +Try to find a spawn point marked 'initial', otherwise +use normal spawn selection. +============ +*/ +/** +* Try to find a spawn point marked 'initial', otherwise +* use normal spawn selection. +*/ +static gentity_t* SelectInitialSpawnPoint( vec3_t origin, vec3_t angles ) { + gentity_t* spot = NULL; + + spot = NULL; + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { + if ( spot->spawnflags & 1 ) { + break; + } + } + + if ( spot == NULL || G_Client_SpotWouldTelefrag( spot ) ) { + return G_Client_SelectSpawnPoint( vec3_origin, origin, angles ); + } + + VectorCopy (spot->s.origin, origin); + origin[2] += 9; + VectorCopy (spot->s.angles, angles); + + return spot; +} + +/* +=========== +SelectSpectatorSpawnPoint + +============ +*/ +static gentity_t* SelectSpectatorSpawnPoint( vec3_t origin, vec3_t angles ) { + FindIntermissionPoint(); + + VectorCopy( level.intermission_origin, origin ); + VectorCopy( level.intermission_angle, angles ); + + return NULL; +} + +/* +======================================================================= + +BODYQUE + +======================================================================= +*/ + +static int32_t bodyFadeSound=0; + + +/* +=============== +G_Client_InitBodyQue +=============== +*/ +void G_Client_InitBodyQue (void) { + int32_t i = 0; + gentity_t* ent = NULL; + + level.bodyQueIndex = 0; + for ( ; iclassname = "bodyque"; + ent->neverFree = qtrue; + level.bodyQue[i] = ent; + } + + if (bodyFadeSound == 0) + { // Initialize this sound. + bodyFadeSound = G_SoundIndex("sound/enemies/borg/walkthroughfield.wav"); + } +} + +/* +============= +BodyRezOut + +After sitting around for five seconds, fade out. +============= +*/ +/** +* After sitting around for five seconds, fade out. +*/ +static void BodyRezOut( gentity_t *ent ) { + if ( level.time - ent->timestamp >= 7500 ) { + // the body ques are never actually freed, they are just unlinked + trap_UnlinkEntity( ent ); + ent->physicsObject = qfalse; + return; + } + + ent->nextthink = level.time + 2500; + ent->s.time = level.time + 2500; + + G_AddEvent(ent, EV_GENERAL_SOUND, bodyFadeSound); +} + +/* +============= +CopyToBodyQue + +A player is respawning, so make an entity that looks +just like the existing corpse to leave behind. +============= +*/ +/** +* A player is respawning, so make an entity that looks +* just like the existing corpse to leave behind. +*/ +static void CopyToBodyQue( gentity_t *ent ) { + gentity_t* body = NULL; + int32_t contents = 0; + entityState_t* eState = NULL; + + trap_UnlinkEntity (ent); + + // if client is in a nodrop area, don't leave the body + contents = trap_PointContents( ent->s.origin, -1 ); + if ( (contents & CONTENTS_NODROP) != 0 ) { + ent->s.eFlags &= ~EF_NODRAW; // Just in case we died from a bottomless pit, reset EF_NODRAW + return; + } + + // grab a body que and cycle to the next one + body = level.bodyQue[ level.bodyQueIndex ]; + level.bodyQueIndex = (level.bodyQueIndex + 1) % BODY_QUEUE_SIZE; + + trap_UnlinkEntity (body); + + eState = &ent->s; + eState->eFlags = EF_DEAD; // clear EF_TALK, etc + eState->powerups = 0; // clear powerups + eState->loopSound = 0; // clear lava burning + eState->number = body - g_entities; + body->timestamp = level.time; + body->physicsObject = qtrue; + body->physicsBounce = 0; // don't bounce + if ( eState->groundEntityNum == ENTITYNUM_NONE ) { + eState->pos.trType = TR_GRAVITY; + eState->pos.trTime = level.time; + VectorCopy( ent->client->ps.velocity, eState->pos.trDelta ); + } else { + eState->pos.trType = TR_STATIONARY; + } + eState->event = 0; + + // change the animation to the last-frame only, so the sequence + // doesn't repeat anew for the body + switch ( eState->legsAnim & ~ANIM_TOGGLEBIT ) { + case BOTH_DEATH1: + case BOTH_DEAD1: + eState->torsoAnim = eState->legsAnim = BOTH_DEAD1; + break; + case BOTH_DEATH2: + case BOTH_DEAD2: + eState->torsoAnim = eState->legsAnim = BOTH_DEAD2; + break; + default: + eState->torsoAnim = eState->legsAnim = BOTH_DEAD1; //DEAD3 + break; + } + + body->r.svFlags = ent->r.svFlags; + VectorCopy (ent->r.mins, body->r.mins); + VectorCopy (ent->r.maxs, body->r.maxs); + VectorCopy (ent->r.absmin, body->r.absmin); + VectorCopy (ent->r.absmax, body->r.absmax); + + body->clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP; + body->r.contents = CONTENTS_CORPSE; + body->r.ownerNum = ent->r.ownerNum; + + body->nextthink = level.time + 5000; + body->think = BodyRezOut; + + body->die = body_die; + + // if there shouldn't be a body, don't show one. + if (ent->client != NULL && + ((level.time - ent->client->ps.powerups[PW_DISINTEGRATE]) < 10000 || + (level.time - ent->client->ps.powerups[PW_EXPLODE]) < 10000)) + { + eState->eFlags |= EF_NODRAW; + } + + // don't take more damage if already gibbed + //RPG-X: RedTechie - Check for medicrevive + if(rpg_medicsrevive.integer == 0){ + if ( ent->health <= GIB_HEALTH ) { + body->takedamage = qfalse; + } else { + body->takedamage = qtrue; + } + } else { + body->takedamage = qfalse; + } + + VectorCopy ( eState->pos.trBase, body->r.currentOrigin ); + trap_LinkEntity (body); +} + +//====================================================================== + + +/* +================== +G_Client_SetViewAngle + +================== +*/ +void G_Client_SetViewAngle( gentity_t *ent, vec3_t angle ) { + int i; + + // set the delta angle + for (i=0 ; i<3 ; i++) { + int cmdAngle; + + cmdAngle = ANGLE2SHORT(angle[i]); + ent->client->ps.delta_angles[i] = cmdAngle - ent->client->pers.cmd.angles[i]; + } + VectorCopy( angle, ent->s.angles ); + VectorCopy (ent->s.angles, ent->client->ps.viewangles); +} + +/* +================ +G_Client_Respawn +================ +*/ +void G_Client_Respawn( gentity_t *ent ) { + qboolean borg = qfalse; + gentity_t* tent = NULL; + playerState_t* ps = NULL; + + CopyToBodyQue (ent); + + G_Client_Spawn(ent, 0, qfalse);//RPG-X: RedTechie - Modifyed + + ps = &ent->client->ps; + + // add a teleportation effect + if ( borg ) { + tent = G_TempEntity( ps->origin, EV_BORG_TELEPORT ); + } else { + tent = G_TempEntity( ps->origin, EV_PLAYER_TRANSPORT_IN ); + ps->powerups[PW_QUAD] = level.time + 4000; + } + + tent->s.clientNum = ent->s.clientNum; +} + +/** + * Get number of clients in team. + * + * \param ignoreClientNum Client to ignore. + * \param team Team. + * \reutrn Number of clients in team. + */ +static team_t G_Client_TeamCount( int ignoreClientNum, int team ) { + int i; + int count = 0; + + for ( i = 0 ; i < level.maxclients ; i++ ) { + if ( i == ignoreClientNum ) { + continue; + } + if ( level.clients[i].pers.connected == CON_DISCONNECTED ) { + continue; + } + if ( level.clients[i].sess.sessionTeam == team ) { + count++; + } + } + + return (team_t)count; +} + +team_t G_Client_PickTeam( int ignoreClientNum ) { + int counts[TEAM_NUM_TEAMS]; + + counts[TEAM_BLUE] = G_Client_TeamCount( ignoreClientNum, TEAM_BLUE ); + counts[TEAM_RED] = G_Client_TeamCount( ignoreClientNum, TEAM_RED ); + + if ( counts[TEAM_BLUE] > counts[TEAM_RED] ) { + return TEAM_RED; + } + if ( counts[TEAM_RED] > counts[TEAM_BLUE] ) { + return TEAM_BLUE; + } + // equal team count, so join the team with the lowest score + if ( level.teamScores[TEAM_BLUE] > level.teamScores[TEAM_RED] ) { + return TEAM_RED; + } + if ( level.teamScores[TEAM_BLUE] < level.teamScores[TEAM_RED] ) { + return TEAM_BLUE; + } + return (team_t)irandom( TEAM_RED, TEAM_BLUE ); +} + +/* +=========== +ForceClientSkin + +Forces a client's skin (for teamplay) +HEAVILY modified for the RPG-X +Player Model system :P +=========== +*/ +/** +* Forces a client's skin (for teamplay) +* HEAVILY modified for the RPG-X +* Player Model system +*/ +static void ForceClientSkin(char* model, const char* skin ) { + char* p = NULL; + char* q = NULL; + + //we expect model to equal 'char/model/skin' + + p = strchr(model, '/'); + + //if no slashes at all + if ( p == NULL || p[0] == 0 || p[1] == 0 ) { + //input everything + strncat(model, "/", MAX_QPATH); + strncat(model, "main", MAX_QPATH); + strncat(model, "/", MAX_QPATH); + strncat(model, skin, MAX_QPATH); + } else { //ie we got a slash (which should be the first of two + p++; + q = strchr(p, '/'); //okay, we should get another one if one was already found + if (q == NULL || q[0] == 0 || q[1] == 0 ) + { //no slashes were found?? >.< + //okay, let's assume they specified the .model file, no skin + //so just add the skin to the end :P + strncat(model, "/", MAX_QPATH); + strncat(model, skin, MAX_QPATH); + } else { + q++; + *q= '\0'; + strncat(model, skin, MAX_QPATH); + } + } +} + +/* +=========== +ClientCheckName +============ +*/ +static void ClientCleanName( const char* in, char* out, int outSize ) { + int32_t len = 0; + int32_t colorlessLen = 0; + char ch = 0; + char* p = NULL; + int32_t spaces = 0; + + //save room for trailing null byte + outSize--; + + p = out; + *p = 0; + spaces = 0; + + while( 1 ) { + ch = *in++; + if( ch == 0 ) { + break; + } + + // don't allow leading spaces + if( !*p && ch == ' ' ) { + continue; + } + + // check colors + if( ch == Q_COLOR_ESCAPE ) { + // solo trailing carat is not a color prefix + if( !*in ) { + break; + } + + // don't allow black in a name, period + if( ColorIndex(*in) == 0 ) { + in++; + continue; + } + + // make sure room in dest for both chars + if( len > outSize - 2 ) { + break; + } + + *out++ = ch; + *out++ = *in++; + len += 2; + continue; + } + + // don't allow too many consecutive spaces + if( ch == ' ' ) { + spaces++; + if( spaces > 3 ) { + continue; + } + } + else { + spaces = 0; + } + + if( len > outSize - 1 ) { + break; + } + + *out++ = ch; + colorlessLen++; + len++; + } + *out = 0; + + // don't allow empty names + if( *p == 0 || colorlessLen == 0 ) + { + Q_strncpyz( p, "RedShirt", outSize ); + } +} + +/* +=========== +legalSkin + +Compare a list of races with an incoming race name. +Used to decide if in a CTF game where a race is specified for a given team if a skin is actually already legal. +=========== +*/ +/** +* Compare a list of races with an incoming race name. +* Used to decide if in a CTF game where a race is specified for a given team if a skin is actually already legal. +*/ +static qboolean legalSkin(const char*race_list, const char* race) +{ + char current_race_name[125]; + const char *s = race_list; + const char *max_place = race_list + strlen(race_list); + const char *marker = NULL; + + memset(current_race_name, 0, sizeof(current_race_name)); + // look through the list till it's empty + while (s < max_place) + { + marker = s; + // figure out from where we are where the next ',' or 0 is + while (*s != ',' && *s != 0) + { + s++; + } + + // copy just that name + strncpy(current_race_name, marker, (s-marker)+1); + + // avoid the comma or increment us past the end of the string so we fail the main while loop + s++; + + // compare and see if this race is the same as the one we want + if (Q_stricmp(current_race_name, race) == 0) + { + return qtrue; + } + } + return qfalse; +} + + +/* +=========== +randomSkin + +given a race name, go find all the skins that use it, and randomly select one +=========== +*/ +/** +* given a race name, go find all the skins that use it, and randomly select one +*/ +static void randomSkin(const char* race, char* model, int32_t current_team, int32_t clientNum) +{ + char** skinsForRace = NULL; + int32_t howManySkins = 0; + int32_t i = 0; + int32_t x = 0; + int32_t temp = 0; + int32_t skin_count_check = 0; + char** skinNamesAlreadyUsed = NULL; + int32_t current_skin_count = 0; + gentity_t* ent = NULL; + char* userinfo = NULL; + char temp_model[MAX_QPATH]; + + memset(temp_model, 0, sizeof(temp_model)); + + skinsForRace = malloc(MAX_SKINS_FOR_RACE * 128 * sizeof(char)); + if(skinsForRace == NULL) { + G_Error("Was unable to allocate %i bytes.\n", MAX_SKINS_FOR_RACE * 128 * sizeof(char)); + return; + } + skinNamesAlreadyUsed = malloc(16 * 128 * sizeof(char)); + if(skinNamesAlreadyUsed == NULL) { + G_Error("Was unable to allocate %i bytes.\n", 16 * 128 * sizeof(char)); + return; + } + + memset(skinsForRace, 0, MAX_SKINS_FOR_RACE * 128 * sizeof(char)); + memset(skinNamesAlreadyUsed, 0, 16 * 128 * sizeof(char)); + + // first up, check to see if we want to select a skin from someone that's already playing on this guys team + skin_count_check = g_random_skin_limit.integer; + if (skin_count_check != 0) { + // sanity check the skins to compare against count + if (skin_count_check > 16) { + skin_count_check = 16; + } + + // now construct an array of the names already used + for ( ; iinuse || i == clientNum) { + continue; + } + + // no, so look at the next one, and see if it's in the list we are constructing + // same team? + if (ent->client != NULL && ent->client->sess.sessionTeam == current_team) { + userinfo = malloc(MAX_INFO_STRING * sizeof(char)); + if(userinfo == NULL) { + G_Error("Was unable to allocate %i bytes.\n", MAX_INFO_STRING * sizeof(char)); + return; + } + // so what's this clients model then? + trap_GetUserinfo( i, userinfo, MAX_INFO_STRING * sizeof(char) ); + Q_strncpyz( temp_model, Info_ValueForKey (userinfo, "model"), sizeof( temp_model ) ); + + free(userinfo); + + // check the name + for (x = 0; x< current_skin_count; x++) { + // are we the same? + if (Q_stricmp(skinNamesAlreadyUsed[x], temp_model) == 0) { + // yeah - ok we already got this one + break; + } + } + + // ok, did we match anything? + if (x == current_skin_count) { + // no - better add this name in + Q_strncpyz(skinNamesAlreadyUsed[current_skin_count], temp_model, sizeof(skinNamesAlreadyUsed[current_skin_count])); + current_skin_count++; + } + } + } + + // ok, array constructed. Did we get enough? + if (current_skin_count >= skin_count_check) { + // yeah, we did - so select a skin from one of these then + temp = rand() % current_skin_count; + Q_strncpyz( model, skinNamesAlreadyUsed[temp], MAX_QPATH ); + ForceClientSkin(model, ""); + free(skinNamesAlreadyUsed); + free(skinsForRace); + return; + } + } + + // search through each and every skin we can find + for (i=0; i 0) { + temp = rand() % howManySkins; + Q_strncpyz( model, skinsForRace[temp], MAX_QPATH ); + } else { + model[0] = 0; + } + + free(skinsForRace); + free(skinNamesAlreadyUsed); +} + +/* +=========== +getNewSkin + +Go away and actually get a random new skin based on a group name +============ +*/ +/** +* Go away and actually get a random new skin based on a group name +*/ +static qboolean getNewSkin(const char* group, char* model, const char* color, const gclient_t* client, int32_t clientNum) +{ + char* temp_string = NULL; + + // go away and get what ever races this skin is attached to. + // remove blue or red name + ForceClientSkin(model, ""); + + temp_string = G_searchGroupList(model); + + // are any of the races legal for this team race? + if (legalSkin(temp_string, group)) { + ForceClientSkin(model, color); + return qfalse; + } + + //if we got this far, then we need to reset the skin to something appropriate + randomSkin(group, model, client->sess.sessionTeam, clientNum); + return qtrue; +} + +/* +=========== +G_Client_UserinfoChanged +============ +*/ +/** +* Called from G_Client_Connect when the player first connects and +* directly by the server system when the player updates a userinfo variable. +* +* The game can override any of the settings and call trap_SetUserinfo +* if desired. +*/ +void G_Client_UserinfoChanged( int32_t clientNum ) { + gentity_t* ent = NULL; + int32_t i = 0; + int32_t modelOffset = 0; + char* s = NULL; + char model[MAX_QPATH]; + char oldname[MAX_STRING_CHARS]; + char userinfo[MAX_INFO_STRING]; + char age[MAX_NAME_LENGTH]; + char race[MAX_NAME_LENGTH]; + char sHeight[10]; + char sWeight[10]; + double weight; + double height; + gclient_t* client = NULL; + qboolean reset; + qboolean changeName = qtrue; //TiM : For the name filter + clientPersistant_t* pers = NULL; + clientSession_t* sess = NULL; + + ent = g_entities + clientNum; + if(ent == NULL) { + return; + } + + memset(model, 0, sizeof(model)); + memset(oldname, 0, sizeof(oldname)); + memset(userinfo, 0, sizeof(userinfo)); + memset(age, 0, sizeof(age)); + memset(race, 0, sizeof(race)); + memset(sHeight, 0, sizeof(sHeight)); + memset(sWeight, 0, sizeof(sWeight)); + client = ent->client; + pers = &client->pers; + sess = &client->sess; + + //TiM - Exit if this user has had their info clamped + if ( (ent->flags & FL_CLAMPED) != 0 ) { + return; + } + + trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); + + // check for malformed or illegal info strings + if ( !Info_Validate(userinfo) ) { + strcpy (userinfo, "\\name\\badinfo"); + } + + // check for local client + s = Info_ValueForKey( userinfo, "ip" ); + if ( strcmp( s, "localhost" ) == 0 ) { + pers->localClient = qtrue; + } + + // check the item prediction + s = Info_ValueForKey( userinfo, "cg_predictItems" ); + if ( atoi( s ) == 0 ) { + pers->predictItemPickup = qfalse; + } else { + pers->predictItemPickup = qtrue; + } + + // set name + //TiM: Filter for if a player is already on this server with that name. + s = Info_ValueForKey (userinfo, "name"); + + if ( (rpg_uniqueNames.integer != 0) && (( ent->r.svFlags & SVF_BOT ) == 0) ) { + char newName[36]; + char activeName[36]; + + memset(newName, 0, sizeof(newName)); + memset(activeName, 0, sizeof(activeName)); + ClientCleanName( s, newName, sizeof(newName) ); + Q_CleanStr( newName ); + + //loop thru all the clients, and see if we have one that has the same name as our proposed one + for ( i = 0; i < level.numConnectedClients; i++ ) { + Q_strncpyz( activeName, g_entities[i].client->pers.netname, sizeof( activeName ) ); + Q_CleanStr( activeName ); + + if ( (g_entities[i].client->ps.clientNum != client->ps.clientNum) + && (Q_stricmp( newName, activeName ) == 0) ) { + trap_SendServerCommand( ent-g_entities, " print \"Unable to change name. A player already has that name on this server.\n\" "); + changeName = qfalse; + break; + } + } + } + + if ( changeName ) { + Q_strncpyz ( oldname, pers->netname, sizeof( oldname ) ); + ClientCleanName( s, pers->netname, sizeof(pers->netname) ); + + if ( sess->sessionTeam == TEAM_SPECTATOR ) { + if ( sess->spectatorState == SPECTATOR_SCOREBOARD ) { + Q_strncpyz( pers->netname, "scoreboard", sizeof(pers->netname) ); + } + } + + if ( pers->connected == CON_CONNECTED ) { + if ( strcmp( oldname, pers->netname ) != 0) { + if ( (!levelExiting) && (level.intermissiontime == 0) ) { + //no need to do this during level changes + trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " renamed to %s\n\"", oldname, pers->netname) ); + } + } + } + } + + pers->pms_height = atof( Info_ValueForKey( userinfo, "height" ) ); + if ( pers->pms_height == 0 ) { + pers->pms_height = 1.0f; + } + + pers->maxHealth = atoi( Info_ValueForKey( userinfo, "handicap" ) ); + if ( pers->maxHealth < 1 || pers->maxHealth > 100 ) { + pers->maxHealth = 100; + } + //if you have a class, ignores handicap and 100 limit, sorry + client->ps.stats[STAT_MAX_HEALTH] = pers->maxHealth; + + Q_strncpyz( model, Info_ValueForKey (userinfo, "model"), sizeof( model ) ); + + // team + + switch( sess->sessionTeam ) { + case TEAM_RED: + { + // decide if we are going to have to reset a skin cos it's not applicable to a race selected + if ((g_gametype.integer < GT_TEAM) || (Q_stricmp("", g_team_group_red.string) == 0)) { + ForceClientSkin(model, "red"); + break; + } else { + // at this point, we are playing CTF and there IS a race specified for this game + reset = getNewSkin(g_team_group_red.string, model, "red", client, clientNum); + + // did we get a model name back? + if (model[0] == 0) { + // no - this almost certainly means we had a bogus race is the g_team_group_team cvar + // so reset it to starfleet and try it again + Com_Printf("WARNING! - Red Group %s is unknown - resetting Red Group to Allow Any Group\n", g_team_group_red.string); + trap_Cvar_Set("g_team_group_red", ""); + trap_Cvar_Register( &g_team_group_red, "g_team_group_red", + "", CVAR_LATCH); + + // Since we are allow any group now, just get his normal model and carry on + Q_strncpyz( model, Info_ValueForKey (userinfo, "model"), sizeof( model ) ); + ForceClientSkin(model, "red"); + reset = qfalse; + } + + if (reset) { + if ( !levelExiting ) { + //no need to do this during level changes + trap_SendServerCommand( -1, va("print \"In-appropriate skin selected for %s on team %s\nSkin selection overridden from skin %s to skin %s\n\"", + pers->netname, g_team_group_red.string, Info_ValueForKey (userinfo, "model"), model)); + } + ForceClientSkin(model, "red"); + // change the value in out local copy, then update it on the server + Info_SetValueForKey(userinfo, "model", model); + trap_SetUserinfo(clientNum, userinfo); + } + break; + } + } + case TEAM_BLUE: + { + // decide if we are going to have to reset a skin cos it's not applicable to a race selected + if ((g_gametype.integer < GT_TEAM) || (Q_stricmp("", g_team_group_blue.string) == 0)) { + ForceClientSkin(model, "blue"); + break; + } else { + // at this point, we are playing CTF and there IS a race specified for this game + // go away and get what ever races this skin is attached to. + reset = getNewSkin(g_team_group_blue.string, model, "blue", client, clientNum); + + // did we get a model name back? + if (model[0] == 0) { + // no - this almost certainly means we had a bogus race is the g_team_group_team cvar + // so reset it to klingon and try it again + Com_Printf("WARNING! - Blue Group %s is unknown - resetting Blue Group to Allow Any Group\n", g_team_group_blue.string); + trap_Cvar_Set("g_team_group_blue", ""); + trap_Cvar_Register( &g_team_group_blue, "g_team_group_blue", + "", CVAR_LATCH ); + + // Since we are allow any group now, just get his normal model and carry on + Q_strncpyz( model, Info_ValueForKey (userinfo, "model"), sizeof( model ) ); + ForceClientSkin(model, "blue"); + reset = qfalse; + } + + if (reset) { + if ( !levelExiting ) { + //no need to do this during level changes + trap_SendServerCommand( -1, va("print \"In-appropriate skin selected for %s on team %s\nSkin selection overridden from skin %s to skin %s\n\"", + pers->netname, g_team_group_blue.string, Info_ValueForKey (userinfo, "model"), model)); + } + ForceClientSkin(model, "blue"); + // change the value in out local copy, then update it on the server + Info_SetValueForKey(userinfo, "model", model); + trap_SetUserinfo(clientNum, userinfo); + } + break; + } + } + default: + break; + } + + if((g_gametype.integer >= GT_TEAM) && (sess->sessionTeam == TEAM_SPECTATOR)) { + // don't ever use a default skin in teamplay, it would just waste memory + ForceClientSkin(model, "red"); + } + + if((rpg_rpg.integer != 0) && (rpg_forceclasscolor.integer != 0) && (g_gametype.integer < GT_TEAM)) { + ForceClientSkin( model, g_classData[sess->sessionClass].modelSkin ); + } + + //TiM : For when an admin chooses not to see admin messages + //Marcin : and check for privacy mode - 24/12/2008 + s = Info_ValueForKey( userinfo, "noAdminChat" ); + if ( atoi( s ) > 0 ) { + client->noAdminChat = qtrue; + } else { + client->noAdminChat = qfalse; + } + + // teamInfo + s = Info_ValueForKey( userinfo, "teamoverlay" ); + if ( ! *s || atoi( s ) != 0 ) { + pers->teamInfo = qtrue; + } else { + pers->teamInfo = qfalse; + } + + //PMS system - lock down the values + s = Info_ValueForKey( userinfo, "height" ); + height = atof( s ); + if (height > (float)rpg_maxHeight.value ) { + Q_strncpyz( sHeight, rpg_maxHeight.string, sizeof( sHeight ) ); + } else if (height < (float)rpg_minHeight.value ) { + Q_strncpyz( sHeight, rpg_minHeight.string, sizeof( sHeight ) ); + } else { + Q_strncpyz( sHeight, s, sizeof( sHeight ) ); + } + + //TiM - needed for height offset + pers->pms_height = atof( sHeight ); + + //PMS system - lock down the values + s = Info_ValueForKey( userinfo, "weight" ); + weight = atof( s ); + if (weight > (float)rpg_maxWeight.value ) { + Q_strncpyz( sWeight, rpg_maxWeight.string, sizeof( sWeight ) ); + } else if (weight < (float)rpg_minWeight.value ) { + Q_strncpyz( sWeight, rpg_minWeight.string, sizeof( sWeight ) ); + } else { + Q_strncpyz( sWeight, s, sizeof( sWeight ) ); + } + + s = Info_ValueForKey( userinfo, "age" ); + Q_strncpyz( age, s, sizeof(age) ); + + s = Info_ValueForKey( userinfo, "race" ); + Q_strncpyz( race, s, sizeof( race ) ); + + s = Info_ValueForKey( userinfo, "modelOffset" ); + modelOffset = atoi( s ); + + // send over a subset of the userinfo keys so other clients can + // print scoreboards, display models, and play custom sounds + //FIXME: In future, we'll lock down these PMS values so we can't have overloaded transmission data + if ( (ent->r.svFlags & SVF_BOT) != 0 ) { + s = va("n\\%s\\t\\%i\\p\\%i\\model\\%s\\hc\\%i\\w\\%i\\l\\%i\\skill\\%s\\age\\25\\height\\%s\\weight\\%s\\race\\Bot\\of\\%i\\admin\\0", + pers->netname, sess->sessionTeam, sess->sessionClass, model, + pers->maxHealth, sess->wins, sess->losses, + Info_ValueForKey( userinfo, "skill" ), + sHeight, sWeight, modelOffset ); + } else { + s = va("n\\%s\\t\\%i\\p\\%i\\model\\%s\\hc\\%i\\w\\%i\\l\\%i\\age\\%s\\height\\%s\\weight\\%s\\race\\%s\\of\\%i\\admin\\%i", + pers->netname, sess->sessionTeam, sess->sessionClass, model, + pers->maxHealth, sess->wins, sess->losses, age, + sHeight, sWeight, race, modelOffset, ((int)G_Client_IsAdmin(g_entities+clientNum))); + } + + trap_SetConfigstring( CS_PLAYERS+clientNum, s ); + + G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, s ); // no ip logging here as string might get to long +} + + +/* +=========== +G_Client_Connect +============ +*/ +/** +* Called when a player begins connecting to the server. +* Called again for every map change or tournement restart. +* +* The session information will be valid after exit. +* +* Return NULL if the client should be allowed, otherwise return +* a string with the reason for denial. +* +* Otherwise, the client will be sent the current gamestate +* and will eventually get to G_Client_Begin. +* +* firstTime will be qtrue the very first time a client connects +* to the server machine, but qfalse on map changes and tournement +* restarts. +*/ +char* G_Client_Connect( int32_t clientNum, qboolean firstTime, qboolean isBot ) { + char* value = NULL; + char* newClass = NULL; + char* newRank = NULL; + char ip[64]; //TiM : Saved the IP data for player recon feature + char userinfo[MAX_INFO_STRING]; + gclient_t* client = NULL; + gentity_t* ent = NULL; + vmCvar_t mapname; + vmCvar_t sv_hostname; + + ent = &g_entities[ clientNum ]; + if(ent == NULL) { + return "Critical Error: Client entity was NULL"; + } + + memset(ip, 0, sizeof(ip)); + memset(userinfo, 0, sizeof(userinfo)); + + trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM ); + trap_Cvar_Register( &sv_hostname, "sv_hostname", "", CVAR_SERVERINFO | CVAR_ROM ); + + trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); + + // check to see if they are on the banned IP list + value = Info_ValueForKey (userinfo, "ip"); + + Q_strncpyz( ip, value, sizeof(ip) ); + + if ( G_FilterPacket( value ) || CheckID( Info_ValueForKey(userinfo, "sv_securityCode" ) ) ) { + return "Banned from this server"; + } + + // check for a password + if ( !isBot ) + { + value = Info_ValueForKey (userinfo, "password"); + if ( g_password.string[0] && Q_stricmp( g_password.string, "none" ) != 0 && strcmp( g_password.string, value) != 0) + { + return "Invalid password"; + } + } + + //TiM: If need be, chack to see if any other players have the current name... + //evil impersonators and the likes + if ( rpg_uniqueNames.integer != 0 && !isBot ) { + char name[36]; + char oldName[36]; + int32_t i = 0; + + memset(name, 0, sizeof(name)); + memset(oldName, 0, sizeof(oldName)); + + //get the name + value = Info_ValueForKey( userinfo, "name" ); + //Clean the data + ClientCleanName( value, name, sizeof( name ) ); + //Now, do a compare with all clients in the server + for (i = 0; i < MAX_CLIENTS; i++ ) { + if ( (g_entities[i].client == NULL) || (g_entities[i].client->pers.connected != CON_CONNECTED) ) { + continue; + } + + if ( g_entities[i].client->pers.netname[0] ) { + + //local copy the string and work on that, else we risk wrecking other people's names + Q_strncpyz( oldName, g_entities[i].client->pers.netname, sizeof( oldName ) ); + if ( (Q_stricmp( Q_CleanStr(name), Q_CleanStr(oldName) ) == 0) && !isBot ) { + return "There is already a user with that name."; + } + } + } + } + + // they can connect + ent->client = level.clients + clientNum; + client = ent->client; + + memset( client, 0, sizeof(*client) ); + + client->pers.connected = CON_CONNECTING; + + // read or initialize the session data + if ( firstTime || level.newSession ) { + G_InitSessionData( client, userinfo ); + } + G_ReadSessionData( client ); + + if( isBot ) { + ent->r.svFlags |= SVF_BOT; + ent->inuse = qtrue; + if( !G_BotConnect( clientNum, !firstTime ) ) { + return "BotConnectfailed"; + } + } + + // get and distribute relevent paramters + G_LogPrintf( "ClientConnect: %i (%s)\n", clientNum, g_entities[clientNum].client->pers.ip ); + if ( rpg_rpg.integer != 0 /*&& firstTime*/ ) + { + //TiM: Code for automatic class + rank switching + //======================================================== + if ( isBot ) { + client->sess.sessionClass = 0; + client->ps.persistant[PERS_SCORE] = 1; + } else { + int32_t tmpScore = 0; + int32_t i = 0; + qboolean changeRank = qfalse; + + newClass = Info_ValueForKey (userinfo, "ui_playerClass" ); + newRank = Info_ValueForKey (userinfo, "ui_playerRank" ); + + //Com_Printf( S_COLOR_RED "Data: %s %s\n", newClass, newRank ); + + if ( newClass[0] != 0 ) { + client->sess.sessionClass = ValueNameForClass ( newClass ); //TiM: BOOYEAH! :) + //if class doesn't exist, default to 0 + if ( client->sess.sessionClass < 0 ) { + client->sess.sessionClass = 0; + } + } else { + client->sess.sessionClass = 0; + } + + for (i = 0; i < MAX_RANKS; i++ ) { + if ( rpg_startingRank.string[0] == 0 && newRank[0] != 0 ) { + if ( Q_stricmp( newRank, g_rankNames[i].consoleName ) == 0 ) { + tmpScore = i;//1 << i; + + if ( rpg_changeRanks.integer != 0 ) { + changeRank = qtrue; + } + break; + } + } else { + if ((rpg_startingRank.string[0] != 0) && (Q_stricmp( g_rankNames[i].consoleName, rpg_startingRank.string ) == 0)) { + tmpScore = i;// 1 << i; + changeRank = qtrue; + break; + } + } + } + + if ( changeRank ) { + ent->client->UpdateScore = qtrue; + G_Client_SetScore( ent, tmpScore ); + } + } + + //======================================================== + } + G_Client_UserinfoChanged( clientNum ); + + //RPG-X: Save the ip for later - has to be down here, since it gets flushed in the above function + Q_strncpyz( ent->client->pers.ip, ip, sizeof( ent->client->pers.ip ) ); + + // don't do the "xxx connected" messages if they were caried over from previous level + if ( firstTime ) + { + if ( !levelExiting ) + {//no need to do this during level changes + qboolean nameFound = qfalse; + + //Check to see if this player already connected on this server + if ( (rpg_renamedPlayers.integer != 0) && ((ent->r.svFlags & SVF_BOT) == 0) ) { + int32_t i = 0; + + for ( i = 0; i < MAX_RECON_NAMES; i++ ) { + if ( g_reconData[i].previousName[0] == 0 ) { + continue; + } + + if ( (Q_stricmp( client->pers.ip, g_reconData[i].ipAddress ) == 0) + && (Q_stricmp( client->pers.netname, g_reconData[i].previousName ) != 0 )) { + trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " (With the previous name of %s" S_COLOR_WHITE ") connected\n\"", client->pers.netname, g_reconData[i].previousName) ); + nameFound = qtrue; + break; + } + } + } + + if ( !nameFound ) { + trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " connected\n\"", client->pers.netname) ); + } + + //RPG-X | Phenix | 07/04/2005 + client->AdminFailed = 0; + ent->n00bCount = 0; + client->LoggedAsDeveloper = qfalse; + } + } + + if ( (g_gametype.integer >= GT_TEAM) && (client->sess.sessionTeam != TEAM_SPECTATOR )) { + BroadcastTeamChange( client, -1 ); + } + + // count current clients and rank for scoreboard + //G_Client_CalculateRanks( qfalse ); + + //RPG-X: J2J - Reset Variables + DragDat[clientNum].AdminId = -1; + DragDat[clientNum].distance = 0; + g_entities[clientNum].client->noclip = qfalse; + + return NULL; +} + +extern holoData_t holoData; + +//! Think function for temporal entity that transmits the holodeck date to the client +static void holoTent_think(gentity_t* ent) { + if(ent->count == 0) { + trap_SendServerCommand(ent-g_entities, va("holo_data %i", holoData.numProgs)); + ent->count = 1; + ent->health = 0; + ent->nextthink = level.time + 250; + return; + } + if(ent->health == holoData.numProgs) { + ent->count++; + ent->health = 0; + } + switch(ent->count) { + case 1: // name + trap_SendServerCommand(ent-g_entities, va("holo_data \"n%i\\%s\\\"", ent->health, holoData.name[ent->health])); + break; + case 2: // desc1 + trap_SendServerCommand(ent-g_entities, va("holo_data \"da%i\\%s\\\"", ent->health, holoData.desc1[ent->health])); + break; + case 3: // desc2 + trap_SendServerCommand(ent-g_entities, va("holo_data \"db%i\\%s\\\"", ent->health, holoData.desc2[ent->health])); + break; + case 4: // image + trap_SendServerCommand(ent-g_entities, va("holo_data \"i%i\\%s\\\"", ent->health, holoData.image[ent->health])); + break; + } + ent->health++; + if(ent->count > 4) { + G_PrintfClient(ent, "Received data of %i holodeck programs.\n", holoData.numProgs); + G_FreeEntity(ent); + return; + } + ent->nextthink = level.time + 250; +} + +//! Create a temporal entity that sends over the holodata to the client +static void G_SendHoloData(int32_t clientNum) { + gentity_t *holoTent; + + holoTent = G_Spawn(); + holoTent->classname = "holoTent"; + + holoTent->target_ent = g_entities + clientNum; + + holoTent->think = holoTent_think; + holoTent->nextthink = level.time + 2500; +} + +//! Think function for temporal entity that transmits the server change data and map change data for transporter UI +static void transTent_think(gentity_t* ent) { + char temp[MAX_STRING_CHARS]; + int32_t i; + + memset(temp, 0, sizeof(temp)); + + for(i = 0; i < level.srvChangeData.count; i++) { + if(temp[0] == 0) { + Com_sprintf(temp, sizeof(temp), "ui_trdata d%i\\%s\\", i, level.srvChangeData.name[i]); + } else { + Com_sprintf(temp, sizeof(temp), "%sd%i\\%s\\", temp, i, level.srvChangeData.name[i]); + } + } + + trap_SendServerCommand(ent->target_ent-g_entities, temp); + G_FreeEntity(ent); +} + +//! creates an entity that transmits the server change data to the client +static void G_SendTransData(int32_t clientNum) { + gentity_t *transTent; + + transTent = G_Spawn(); + transTent->classname = "transTent"; + + transTent->target_ent = g_entities + clientNum; + + transTent->think = transTent_think; + transTent->nextthink = level.time + 500; +} + +/* +=========== +G_Client_Begin +============ +*/ +/** +* called when a client has finished connecting, and is ready +* to be placed into the level. This will happen every level load, +* and on transition between teams, but doesn't happen on respawns +*/ +void G_Client_Begin( int32_t clientNum, qboolean careAboutWarmup, qboolean isBot, qboolean first ) { + gentity_t* ent = NULL; + gclient_t* client = NULL; + gentity_t* tent = NULL; + gentity_t* selfdestruct = NULL; + int32_t flags = 0; + qboolean alreadyIn = qfalse; + int32_t score = 0; + + ent = g_entities + clientNum; + if(ent == NULL) { + return; + } + + if( ent->botDelayBegin ) { + G_QueueBotBegin( clientNum ); + ent->botDelayBegin = qfalse; + return; + } + + client = level.clients + clientNum; + + if ( ent->r.linked ) { + trap_UnlinkEntity( ent ); + } + G_InitGentity( ent ); + ent->touch = 0; + ent->pain = 0; + ent->client = client; + + if ( client->pers.connected == CON_CONNECTED ) { + alreadyIn = qtrue; + } + client->pers.connected = CON_CONNECTED; + client->pers.enterTime = level.time; + client->pers.teamState.state = TEAM_BEGIN; + + // save eflags around this, because changing teams will + // cause this to happen with a valid entity, and we + // want to make sure the teleport bit is set right + // so the viewpoint doesn't interpolate through the + // world to the new position + + //TiM... I think this is why my damn RANK SYSTEM ENHANCEMENT HAS BEEN BUGGING OUT!!@!@!! + //ARRRGRGRGRGRGRGRGRGRGRGRGHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH!!!!! D:< + flags = client->ps.eFlags; + score = client->ps.persistant[PERS_SCORE]; + memset( &client->ps, 0, sizeof( client->ps ) ); + client->ps.eFlags = flags; + + client->UpdateScore = qtrue; + G_Client_SetScore( ent, score ); + + // locate ent at a spawn point + G_Client_Spawn( ent, 0, qfalse );//RPG-X: RedTechie - Modifyed + + if ( client->sess.sessionTeam != TEAM_SPECTATOR) { + // Don't use transporter FX for spectators or those watching the holodoors. + // send event + + ent->client->ps.powerups[PW_QUAD] = level.time + 4000; + tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TRANSPORT_IN ); + tent->s.clientNum = ent->s.clientNum; + } + G_LogPrintf( "ClientBegin: %i (%s)\n", clientNum, g_entities[clientNum].client->pers.ip ); + + // count current clients and rank for scoreboard + G_Client_CalculateRanks( qfalse ); + + //TiM - This appears to be a flaw in Raven's design + //When a client connects, or if they enter admin or medics class + //ensure the relevant health data is sent to them, or else they'll + //see anomalies when scanning players + if ( (client->sess.sessionTeam == TEAM_SPECTATOR) || + (g_classData[client->sess.sessionClass].isMedical) || + (g_classData[client->sess.sessionClass].isAdmin) ) + { + int32_t i = 0; + char entry[16]; + char command[1024]; + int32_t numPlayers = 0; + gentity_t *player; + int32_t len = 0; + int32_t cmdLen=0; + + memset(entry, 0, sizeof(entry)); + memset(command, 0, sizeof(command)); + + for ( ; i < g_maxclients.integer; i++ ) + { + player = g_entities + i; + + if ( (player == NULL) || (player == ent) || !player->inuse ) { + continue; + } + + Com_sprintf( entry, sizeof(entry), " %i %i", i, player->health >= 0 ? player->health : 0 ); + len = strlen( entry ); + if ( cmdLen + len > sizeof( command ) ) { + break; + } + strcpy( command + cmdLen, entry ); + cmdLen += len; + + numPlayers++; + } + + if ( numPlayers > 0 ) { + trap_SendServerCommand( clientNum, va("hinfo %i%s", numPlayers, command) ); + } + } + + //RPG-X: RedTechie - But we dont care about warmup! + if ( careAboutWarmup ) { + if (level.restarted || g_restarted.integer != 0) { + trap_Cvar_Set( "g_restarted", "0" ); + level.restarted = qfalse; + } + } + + //RPG-X | Phenix | 21/11/2004 + //BOOKMARK FOR INIT + if(!alreadyIn) { + // RPG-X | Phenix | 06/04/2005 + ent->client->n00bTime = -1; + } + + ent->client->fraggerTime = -1; + + // kef -- should reset all of our awards-related stuff + G_ClearClientLog(clientNum); + + //TiM - if our user's security key was default, transmit the received IP bak to + //the client and get it to encode it into our new key + + //Scooter's filter list + if( Q_stricmp( ent->client->pers.ip, "localhost" ) != 0 //localhost + && Q_strncmp( ent->client->pers.ip, "10.", 3 ) != 0 //class A + && Q_strncmp( ent->client->pers.ip, "172.16.", 7 ) != 0 //class B + && Q_strncmp( ent->client->pers.ip, "192.168.", 8 ) != 0 //class C + && Q_strncmp( ent->client->pers.ip, "127.", 4 ) != 0 //loopback + && Q_strncmp( ent->client->pers.ip, "169.254.", 8 ) != 0 //link-local + ) + { + char userInfo[MAX_TOKEN_CHARS]; + uint64_t securityID = 0; + + memset(userInfo, 0, sizeof(userInfo)); + + trap_GetUserinfo( clientNum, userInfo, sizeof( userInfo ) ); + if ( userInfo[0] == 0 ) { + return; + } + + securityID = (uint64_t)atoul( Info_ValueForKey( userInfo, "sv_securityCode" ) ); + + if ( (securityID <= 0) || (securityID >= 0xffffffff) ) { + trap_SendServerCommand( clientNum, va( "configID %s", ent->client->pers.ip ) ); + } + } + + // send srv change data to ui + if(!isBot && first) { + if(level.srvChangeData.count > 0) { + G_SendTransData(clientNum); + } + } + + // send holo data to ui + if(!isBot && first) { + if(holoData.numProgs) { + G_SendHoloData(clientNum); + } + } + + //RPG-X: Marcin: show the server motd - 15/12/2008 + if ( !isBot && first ) { + trap_SendServerCommand( ent->s.number, "motd" ); + } + + if ( !isBot ) { + qboolean last = qfalse; + int32_t len = 0; + fileHandle_t file = 0; + char* p = NULL; + char* q = NULL; + char buf[16000]; // TODO move to heap ? + + len = trap_FS_FOpenFile( rpg_motdFile.string, &file, FS_READ ); + if (file == 0 || len == 0) { + trap_SendServerCommand( ent->s.number, va("motd_line \"^1%s not found or empty^7\"", rpg_motdFile.string) ); + return; + } + + memset(buf, 0, sizeof(buf)); + trap_FS_Read( buf, len, file ); + + p = &buf[0]; + q = p; + buf[len] = '\0'; + while ( !last ) { + p = q; + while ( *q != '\n' ) { + if ( !*q ) { + last = qtrue; + } + if ( ( *q == ' ' ) && ( EndWord( q ) - p ) > 78 ) { + break; + } + q++; + } + *q = '\0'; + + trap_SendServerCommand( ent->s.number, va( "motd_line \"%s\"", p ) ); + + q++; + } + + } + //we may currently be selfdestructing, so send stuff for that case + selfdestruct = G_Find(NULL, FOFS(classname), "target_selfdestruct"); + if((selfdestruct != NULL) && ((selfdestruct->spawnflags & 1) != 0)) { + trap_SendServerCommand(ent->s.number, va("selfdestructupdate %i", selfdestruct->damage - level.time)); + } else { + trap_SendServerCommand(ent->s.number, va("selfdestructupdate %i", -1)); + } +} + +/** + * Get weapons for a class. + * + * \param client The client. + * \param pclass Class to get weapons for. + * \author PHENIX1 + */ +static void G_Client_WeaponsForClass ( gclient_t* client, pclass_t pclass ) +{ + int32_t i = WP_1; + int32_t Bits = 0; + + Bits = ( 1 << WP_1); + Bits |= g_classData[pclass].weaponsFlags; + + for ( ; i < MAX_WEAPONS; i++ ) { + //if we want no weapons and aren't an admin, skip this particular weapon + if ( (rpg_noweapons.integer != 0) && !g_classData[pclass].isAdmin ) { + if ( (i >= WP_5) && (i <= WP_10) ) { + continue; + } + } + + if ( (Bits & ( 1 << i )) != 0 ) { + client->ps.stats[STAT_WEAPONS] |= ( 1 << i ); + client->ps.ammo[i] = Min_Weapon(i); + } + } +} + +/** + * Get holdable items for a class. + * + * \param client The client. + * \param pclass Class for which to get holdables. + */ +static void G_Client_HoldablesForClass ( gclient_t* client, pclass_t pclass ) +{ + if ( g_classData[pclass].isMarine ) { + client->ps.stats[STAT_HOLDABLE_ITEM] = BG_FindItemForHoldable( HI_TRANSPORTER ) - bg_itemlist; + } else if ( g_classData[pclass].isAdmin ) { + client->ps.stats[STAT_HOLDABLE_ITEM] = BG_FindItemForHoldable( HI_SHIELD ) - bg_itemlist; + } +} + +void G_Client_StoreClientInitialStatus( gentity_t* ent ) +{ + char userinfo[MAX_INFO_STRING]; + + if ( clientInitialStatus[ent->s.number].initialized ) { + //already set + return; + } + + if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { + //don't store their data if they're just a spectator + return; + } + + memset(userinfo, 0, sizeof(userinfo)); + trap_GetUserinfo( ent->s.number, userinfo, sizeof( userinfo ) ); + Q_strncpyz( clientInitialStatus[ent->s.number].model, Info_ValueForKey (userinfo, "model"), sizeof( clientInitialStatus[ent->s.number].model ) ); + clientInitialStatus[ent->s.number].pClass = ent->client->sess.sessionClass; + clientInitialStatus[ent->s.number].team = ent->client->sess.sessionTeam; + clientInitialStatus[ent->s.number].initialized = qtrue; + ent->client->classChangeDebounceTime = 0; +} + +/* +=========== +G_Client_Spawn + +Called every time a client is placed fresh in the world: +after the first G_Client_Begin, and after each respawn +Initializes all non-persistant parts of playerState +------------------------------------ +Modifyed By: RedTechie +And also by Marcin - 30/12/2008 +============ +*/ +void G_Client_Spawn(gentity_t* ent, int32_t rpgx_spawn, qboolean fromDeath ) { + int32_t index = 0; + int32_t i = 0; + int32_t persistant[MAX_PERSISTANT]; + int32_t flags = 0; + int32_t savedPing = 0; + int32_t cCDT = 0; + int32_t clientNum = 0; + vec3_t spawn_origin; + vec3_t spawn_angles; + gclient_t* client = NULL; + clientPersistant_t saved; + clientSession_t savedSess; + gentity_t* spawnPoint = NULL; + pclass_t pClass = 0; + pclass_t oClass = 0; + + if(ent == NULL) { + return; + } + + memset(persistant, 0, sizeof(persistant)); + index = ent - g_entities; + client = ent->client; + clientNum = ent->client->ps.clientNum; + + // find a spawn point + // do it before setting health back up, so farthest + // ranging doesn't count this client + if(rpgx_spawn != 1){//RPG-X: RedTechie - Make sure the spawn is regular spawn or spawn at current position (rpgx_spawn = current possition) + if ( client->sess.sessionTeam == TEAM_SPECTATOR ) { + spawnPoint = SelectSpectatorSpawnPoint (spawn_origin, spawn_angles); + } else { + do { + // the first spawn should be at a good looking spot + if ( !client->pers.initialSpawn && client->pers.localClient ) { + client->pers.initialSpawn = qtrue; + spawnPoint = SelectInitialSpawnPoint( spawn_origin, spawn_angles ); + } else { + // don't spawn near existing origin if possible + spawnPoint = G_Client_SelectSpawnPoint (client->ps.origin, spawn_origin, spawn_angles); + } + + // Tim needs to prevent bots from spawning at the initial point + // on q3dm0... + if ( (( spawnPoint->flags & FL_NO_BOTS ) != 0) && (( ent->r.svFlags & SVF_BOT ) != 0) ) { + continue; // try again + } + // just to be symetric, we have a nohumans option... + if ( (( spawnPoint->flags & FL_NO_HUMANS ) != 0) && (( ent->r.svFlags & SVF_BOT ) == 0) ) { + continue; // try again + } + + break; + + } while ( 1 ); + } + } //RPG-X: RedTechie - End rpgx_spawn check + client->pers.teamState.state = TEAM_ACTIVE; + + // toggle the teleport bit so the client knows to not lerp + if(rpgx_spawn != 1){ + flags = ent->client->ps.eFlags & EF_TELEPORT_BIT; + flags ^= EF_TELEPORT_BIT; + } + + // clear everything but the persistant data + + saved = client->pers; + savedSess = client->sess; + savedPing = client->ps.ping; + for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) { + persistant[i] = client->ps.persistant[i]; + } + //okay, this is hacky, but we need to keep track of this, even if uninitialized first time you spawn, it will be stomped anyway + //RPG-X: RedTechie - Damn thing screwed my function up + if(rpgx_spawn != 1){ + if ( client->classChangeDebounceTime ) { + cCDT = client->classChangeDebounceTime; + } + memset (client, 0, sizeof(*client)); + client->classChangeDebounceTime = cCDT; + } + // + client->pers = saved; + client->sess = savedSess; + client->ps.ping = savedPing; + for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) { + client->ps.persistant[i] = persistant[i]; + } + + // increment the spawncount so the client will detect the respawn + if(rpgx_spawn != 1) { + client->ps.persistant[PERS_SPAWN_COUNT]++; + client->airOutTime = level.time + 12000; + } + + if(client->sess.sessionTeam != TEAM_SPECTATOR) { + client->sess.sessionTeam = TEAM_FREE; + } + client->ps.persistant[PERS_TEAM] = client->sess.sessionTeam; + + // clear entity values + client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; + client->ps.eFlags = flags; + client->streakCount = 0; + + ent->client->ps.pm_type = PM_NORMAL; + ent->s.groundEntityNum = ENTITYNUM_NONE; + ent->client = &level.clients[index]; + ent->takedamage = qtrue; + ent->inuse = qtrue; + ent->classname = "player"; + + ent->r.contents = CONTENTS_BODY; + + ent->clipmask = MASK_PLAYERSOLID; + ent->die = G_Client_Die; + ent->waterlevel = 0; + ent->watertype = 0; + ent->flags = 0; + + if(rpgx_spawn != 1) { + VectorCopy (playerMins, ent->r.mins); + VectorCopy (playerMaxs, ent->r.maxs); + } + + client->ps.clientNum = index; + + // health will count down towards max_health + ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] * 1.25; + + oClass = client->sess.sessionClass; + + if ( oClass != client->sess.sessionClass ) { + //need to send the class change + G_Client_UserinfoChanged( client->ps.clientNum ); + } + + client->ps.persistant[PERS_CLASS] = client->sess.sessionClass; + pClass = client->sess.sessionClass; + + if ( pClass != 0 ) { + //no health boost on spawn for playerclasses + ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; + } + + if ( !fromDeath || (rpg_dropOnDeath.integer == 0) || (rpg_allowWeaponDrop.integer == 0) ) { + G_Client_WeaponsForClass( client, pClass ); + } else { // Marcin: just a hand + G_Client_WeaponsForClass( client, 0 ); + } + G_Client_HoldablesForClass( client, pClass ); + + if(rpgx_spawn != 1) { + G_SetOrigin( ent, spawn_origin ); + VectorCopy( spawn_origin, client->ps.origin ); + } + + // the respawned flag will be cleared after the attack and jump keys come up + if(rpgx_spawn != 1) { + client->ps.pm_flags |= PMF_RESPAWNED; + } + + trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd ); + if(rpgx_spawn != 1){ + G_Client_SetViewAngle( ent, spawn_angles ); + } + + if(rpgx_spawn != 1) { + if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { + + } else { + G_MoveBox( ent ); + trap_LinkEntity (ent); + + // force the base weapon up + client->ps.weapon = WP_1; //TiM: WP_5 + client->ps.weaponstate = WEAPON_READY; + + } + } + + // don't allow full run speed for a bit + client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + client->ps.pm_time = 100; + + if(rpgx_spawn != 1) { + client->respawnTime = level.time; + } + + client->inactivityTime = level.time + g_inactivity.integer * 1000; + client->latched_buttons = 0; + + // set default animations + if (rpgx_spawn != 1 ) { + client->ps.stats[TORSOANIM] = BOTH_STAND1; + client->ps.stats[LEGSANIM] = BOTH_STAND1; + } + + if ( level.intermissiontime ) { + MoveClientToIntermission( ent ); + } else { + // fire the targets of the spawn point + if(rpgx_spawn != 1) { + G_UseTargets( spawnPoint, ent ); + } + + // select the highest weapon number available, after any + // spawn given items have fired + client->ps.weapon = 1; + + //TiM - Always default to the null hand + client->ps.weapon = WP_1; + } + + // run a client frame to drop exactly to the floor, + // initialize animations and other things + client->ps.commandTime = level.time - 100; + ent->client->pers.cmd.serverTime = level.time; + ClientThink( ent-g_entities ); + + // positively link the client, even if the command times are weird + if ( ent->client->sess.sessionTeam != TEAM_SPECTATOR ) { + BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); + if(rpgx_spawn != 1) { + VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); + } + trap_LinkEntity( ent ); + + } + + // run the presend to set anything else + ClientEndFrame( ent ); + + // clear entity state values + BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); + + //start-up messages + //FIXME: externalize all this text! + //FIXME: make the gametype titles be graphics! + //FIXME: make it do this on a map_restart also + if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { + //spectators just get the title of the game + switch ( g_gametype.integer ) { + case GT_FFA: // free for all + trap_SendServerCommand( ent-g_entities, va("cp \"%s\"", rpg_welcomemessage.string ) ); + break; + case GT_TOURNAMENT: // one on one tournament + trap_SendServerCommand( ent-g_entities, va("cp \"%s\"", rpg_welcomemessage.string ) ); + break; + case GT_SINGLE_PLAYER: // single player tournament + trap_SendServerCommand( ent-g_entities, va("cp \"%s\"", rpg_welcomemessage.string ) ); + break; + case GT_TEAM: // team deathmatch + trap_SendServerCommand( ent-g_entities, va("cp \"%s\"", rpg_welcomemessage.string ) ); + break; + case GT_CTF: // capture the flag + trap_SendServerCommand( ent-g_entities, va("cp \"%s\"", rpg_welcomemessage.string ) ); + break; + } + } else { + if ( !clientInitialStatus[ent->s.number].initialized ) { + //first time coming in + switch ( g_gametype.integer ) { + case GT_FFA: // free for all + trap_SendServerCommand( ent-g_entities, va("cp \"%s\"", rpg_welcomemessage.string ) ); + break; + case GT_TOURNAMENT: // one on one tournament + trap_SendServerCommand( ent-g_entities, va("cp \"%s\"", rpg_welcomemessage.string ) ); + break; + case GT_SINGLE_PLAYER: // single player tournament + trap_SendServerCommand( ent-g_entities, va("cp \"%s\"", rpg_welcomemessage.string ) ); + break; + case GT_TEAM: // team deathmatch + trap_SendServerCommand( ent-g_entities, va("cp \"%s\"", rpg_welcomemessage.string ) ); + break; + case GT_CTF: // capture the flag + trap_SendServerCommand( ent-g_entities, va("cp \"%s\"", rpg_welcomemessage.string ) ); + break; + } + } + } + + if(rpgx_spawn != 0) { + if ( (client->sess.sessionTeam == TEAM_SPECTATOR) || + g_classData[client->sess.sessionClass].isMedical || + g_classData[client->sess.sessionClass].isAdmin ) { + int32_t l = 0; + int32_t numPlayers = 0; + int32_t len = 0; + int32_t cmdLen=0; + char entry[16]; + char command[1024]; + gentity_t* player = NULL; + + memset(entry, 0, sizeof(entry)); + memset(command, 0, sizeof(entry)); + + for ( ; l < g_maxclients.integer; l++ ) + { + player = g_entities + l; + + if ( (player == NULL) || (player == ent) || !player->inuse ) { + continue; + } + + Com_sprintf( entry, sizeof(entry), " %i %i", l, player->health >= 0 ? player->health : 0 ); + len = strlen( entry ); + if ( cmdLen + len > sizeof( command ) ) { + break; + } + strcpy( command + cmdLen, entry ); + cmdLen += len; + + numPlayers++; + } + + if ( numPlayers > 0 ) { + trap_SendServerCommand( clientNum, va("hinfo %i%s", numPlayers, command) ); + } + } + } + + //store intial client values + //FIXME: when purposely change teams, this gets confused? + + G_Client_StoreClientInitialStatus( ent ); + + //RPG-X: Marcin: stuff was here previously - 22/12/2008 +} + +/* +=========== +G_Client_Disconnect + +Called when a player drops from the server. +Will not be called between levels. + +This should NOT be called directly by any game logic, +call trap_DropClient(), which will call this and do +server system housekeeping. +============ +*/ +void G_Client_Disconnect( int32_t clientNum ) { + gentity_t* ent = NULL; + gentity_t* tent = NULL; + int32_t i = 0; + + ent = &g_entities[clientNum]; + if ( (ent == NULL) || (ent->client == NULL) ) { + return; + } + + // stop any following clients + for ( i = 0 ; i < level.maxclients ; i++ ) { + if ( level.clients[i].sess.sessionTeam == TEAM_SPECTATOR + && level.clients[i].sess.spectatorState == SPECTATOR_FOLLOW + && level.clients[i].sess.spectatorClient == clientNum ) { + StopFollowing( &g_entities[i] ); + } + } + + //RPG-X: J2J - Stop any dragging. + DragDat[clientNum].AdminId = -1; + DragDat[clientNum].distance = 0; + g_entities[clientNum].client->noclip = qfalse; + + //TiM: Log the player's IP and name. If they reconnect again, it'll announce their deceipt >:) + if ( (rpg_renamedPlayers.integer != 0) && ((ent->r.svFlags & SVF_BOT) == 0) ) { + int32_t l = 0; + qboolean foundName = qfalse; + + //Do a chek to see if this player has disconnected b4. else we'll be wasting a slot. + for ( ; l < MAX_RECON_NAMES; l++ ) { + if ( g_reconData[l].ipAddress[0] == 0 ) { + continue; + } + + if ( Q_stricmp( ent->client->pers.ip, g_reconData[l].ipAddress ) == 0 ) { + foundName=qtrue; + break; + } + } + + if ( foundName ) { + memset( &g_reconData[l], 0, sizeof( g_reconData[l] ) ); + + //IP Address + Q_strncpyz( g_reconData[l].ipAddress, ent->client->pers.ip, sizeof( g_reconData[l].ipAddress ) ); + //Player Name + Q_strncpyz( g_reconData[l].previousName, ent->client->pers.netname, sizeof( g_reconData[l].previousName ) ); + } else { + memset( &g_reconData[g_reconNum], 0, sizeof( g_reconData[g_reconNum] ) ); + + //IP Address + Q_strncpyz( g_reconData[g_reconNum].ipAddress, ent->client->pers.ip, sizeof( g_reconData[g_reconNum].ipAddress ) ); + //Player Name + Q_strncpyz( g_reconData[g_reconNum].previousName, ent->client->pers.netname, sizeof( g_reconData[g_reconNum].previousName ) ); + + g_reconNum++; + //cap reconNum just in case. + + if ( g_reconNum >= MAX_RECON_NAMES ) { + g_reconNum = 0; + } + } + } + + // send effect if they were completely connected + if ( (ent->client->pers.connected == CON_CONNECTED) + && (ent->client->sess.sessionTeam != TEAM_SPECTATOR) ) { + vec3_t org; + + VectorCopy( ent->client->ps.origin, org ); + org[2] += (ent->client->ps.viewheight >> 1); + + tent = G_TempEntity( org, EV_PLAYER_TELEPORT_OUT ); + tent->s.clientNum = ent->s.clientNum; + + // They don't get to take powerups with them! + // Especially important for stuff like CTF flags + G_Client_TossClientItems ( ent, qtrue ); + } + G_LogPrintf( "ClientDisconnect: %i (%s)\n", clientNum, g_entities[clientNum].client->pers.ip ); + + // if we are playing in tourney mode and losing, give a win to the other player + if ( (g_gametype.integer == GT_TOURNAMENT) && (level.intermissiontime == 0) + && (level.warmupTime == 0) && (level.sortedClients[1] == clientNum) ) { + level.clients[ level.sortedClients[0] ].sess.wins++; + G_Client_UserinfoChanged( level.sortedClients[0] ); + } + + if ( (g_gametype.integer == GT_TOURNAMENT) && (ent->client->sess.sessionTeam == TEAM_FREE) && (level.intermissiontime != 0) ) { + trap_SendConsoleCommand( EXEC_APPEND, "map_restart 0\n" ); + level.restarted = qtrue; + level.intermissiontime = 0; + } + + trap_UnlinkEntity (ent); + memset( ent, 0, sizeof( ent ) ); + ent->s.modelindex = 0; + ent->inuse = qfalse; + ent->classname = "disconnected"; + ent->client->pers.connected = CON_DISCONNECTED; + ent->client->ps.persistant[PERS_TEAM] = TEAM_FREE; + ent->client->ps.persistant[PERS_CLASS] = 0;//PC_NOCLASS; + ent->client->sess.sessionTeam = TEAM_FREE; + ent->client->sess.sessionClass = 0;//PC_NOCLASS; + + trap_SetConfigstring( CS_PLAYERS + clientNum, ""); + + G_Client_CalculateRanks( qfalse ); + + if ( (ent->r.svFlags & SVF_BOT) != 0 ) { + BotAIShutdownClient( clientNum ); + } + + // kef -- if this guy contributed to any of our kills/deaths/weapons logs, clean 'em out + G_ClearClientLog(clientNum); + + //also remove any initial data + clientInitialStatus[clientNum].initialized = qfalse; +} + +/* +================ +IsAdmin +RPG-X | Phenix | 21/11/2004 +================ +*/ +/** +* Checks if player is an admin. +* \param ent the player +* +* \author Ubergames - Phenix +* \date 21/11/2004 +*/ +qboolean G_Client_IsAdmin( gentity_t *ent) +{ + if ( ent == NULL ) { + return qfalse; + } + + if ( ent->client == NULL ) { + return qfalse; + } + + if (( g_classData[ent->client->sess.sessionClass].isAdmin ) || + ( ent->client->LoggedAsAdmin == qtrue ) || + ( ent->client->LoggedAsDeveloper == qtrue )) { + return qtrue; + } else { + return qfalse; + } +} + +/* +=========== +G_Client_GetLocation + +Report a location for the player. Uses placed nearby target_location entities +============ +*/ +static gentity_t* G_Client_GetLocation(gentity_t* ent) { + gentity_t* eloc = NULL; + gentity_t* best = NULL; + double bestlen = 0.0; + double len = 0.0; + vec3_t origin; + + bestlen = 3*8192.0*8192.0; + + VectorCopy( ent->r.currentOrigin, origin ); + + for (eloc = level.locationHead; eloc; eloc = eloc->nextTrain) { + len = ( origin[0] - eloc->r.currentOrigin[0] ) * ( origin[0] - eloc->r.currentOrigin[0] ) + + ( origin[1] - eloc->r.currentOrigin[1] ) * ( origin[1] - eloc->r.currentOrigin[1] ) + + ( origin[2] - eloc->r.currentOrigin[2] ) * ( origin[2] - eloc->r.currentOrigin[2] ); + + if ( len > bestlen ) { + continue; + } + + if ( !trap_InPVS( origin, eloc->r.currentOrigin ) ) { + continue; + } + + bestlen = len; + best = eloc; + } + + return best; +} + +/* +=========== +G_Client_GetLocationMsg + +Report a location for the player. Uses placed nearby target_location entities +============ +*/ +qboolean G_Client_GetLocationMsg(gentity_t *ent, char *loc, int32_t loclen) +{ + gentity_t* best = NULL; + + best = G_Client_GetLocation( ent ); + + if (best == NULL) { + return qfalse; + } + + if (best->count != 0) { + if (best->count < 0) + best->count = 0; + if (best->count > 7) + best->count = 7; + Com_sprintf(loc, loclen, "%c%c%s" S_COLOR_WHITE, Q_COLOR_ESCAPE, best->count + '0', best->message ); + } else { + Com_sprintf(loc, loclen, "%s", best->message); + } + + return qtrue; +} + +/* +================== +G_Client_CheckHealthInfoMessage + +Sends Health Changes to proper clients + +Format: + clientNum health + +================== +*/ +static void G_Client_CheckHealthInfoMessage( void ) { + char entry[1024]; + char string[1400]; + int32_t stringlength = 0; + int32_t i = 0; + int32_t j = 0; + int32_t t = 0; + int32_t sendToCnt = 0; + int32_t cnt = 0; + int32_t sentCnt = 0; + int32_t h = 0; + int32_t clients[MAX_CLIENTS]; + int32_t sendToClients[MAX_CLIENTS]; + gentity_t* player = NULL; + gentity_t* ent = NULL; + + memset(entry, 0, sizeof(entry)); + memset(string, 0, sizeof(string)); + memset(clients, 0, sizeof(clients)); + memset(sendToClients, 0, sizeof(sendToClients)); + + //only send this to medics or spectators or adminz + for ( ; i < g_maxclients.integer; i++) { + + if ( (level.clients[i].pers.connected == CON_CONNECTED) && (level.clients[i].ps.stats[STAT_HEALTH] > 0) && //make sure they've actually spawned in already + ((level.clients[i].sess.sessionTeam == TEAM_SPECTATOR) || (g_classData[level.clients[i].sess.sessionClass].isMedical) || (g_classData[level.clients[i].sess.sessionClass].isAdmin) ) ) { + sendToClients[sendToCnt++] = i; + } + } + + if ( sendToCnt == 0 ) { + //no-one to send to + return; + } + + //only send those clients whose health has changed this cycle + //NB: there's a prob with client 0 in here.... + for (i = 0, cnt = 0; i < g_maxclients.integer; i++) { + player = g_entities + i; + if ( (player != NULL) && player->inuse && (player->old_health != player->health) && ( (player->health > 0) || (player->old_health > 0) )) { + clients[cnt++] = i; + player->old_health = player->health; + } + } + + if ( cnt == 0 ) { + //no-one relevant changed health + return; + } + + for ( t = 0; t < sendToCnt; t++ ) { + ent = g_entities + sendToClients[t]; + sentCnt = 0; + + // send the latest information on all clients + string[0] = 0; + stringlength = 0; + + for (i = 0; i < cnt; i++) { + player = g_entities + clients[i]; + + if ( ent == player ) {//don't send the ent his own health + continue; + } + + //send this one + sentCnt++; + + h = player->health; + if (h < 0) h = 0; + + Com_sprintf (entry, sizeof(entry), " %i %i", clients[i], h); + j = strlen(entry); + if (stringlength + j > sizeof(string)) + break; + strcpy (string + stringlength, entry); + stringlength += j; + } + + if ( sentCnt != 0 ) { + trap_SendServerCommand( sendToClients[t], va("hinfo %i%s", sentCnt, string) ); + } + } +} + +/** + * Send client location information. + * + * \param ent The client. + */ +static void G_Client_LocationsMessage( gentity_t *ent ) { + char entry[1024]; + char string[1400]; + int32_t stringlength = 0; + int32_t i = 0; + int32_t j = 0; + int32_t cnt = 0; + gentity_t* player = NULL; + + //don't bother sending during intermission? + if ( level.intermissiontime != 0 ) { + return; + } + + memset(entry, 0, sizeof(entry)); + memset(string, 0, sizeof(string)); + + // figure out what client should be on the display + // we are limited to 8, but we want to use the top eight players + // but in client order (so they don't keep changing position on the overlay) + for ( ; i < g_maxclients.integer && cnt < TEAM_MAXOVERLAY; i++) { + player = g_entities + level.sortedClients[i]; + if ((player != NULL) && (player->inuse) && (player->client->sess.sessionTeam == ent->client->sess.sessionTeam) ) { + // TODO huh? remove? + } + } + + // send the latest information on all clients + for (i = 0, cnt = 0; i < g_maxclients.integer && cnt < TEAM_MAXOVERLAY; i++) { + player = g_entities + i; + //RPG-X | Phenix | 05/03/2005 + if ((player != NULL) && player->inuse) { + //to counter for the fact we could pwn the server doing this, remove all superfluous data + + Com_sprintf (entry, sizeof(entry), " %i %i ", i, player->client->pers.teamState.location); + j = strlen(entry); + if (stringlength + j > sizeof(string)) { + break; + } + strcpy (string + stringlength, entry); + stringlength += j; + cnt++; + } + } + + trap_SendServerCommand( ent-g_entities, va("tinfo %i%s", cnt, string) ); +} + +//TiM - Modified to work with RPG-X +void G_Client_CheckClientStatus(void) { + gentity_t* loc = NULL; + gentity_t* ent = NULL; + + if (level.time - level.lastTeamLocationTime > TEAM_LOCATION_UPDATE_TIME) { + int32_t i = 0; + + level.lastTeamLocationTime = level.time; + + for ( ; i < g_maxclients.integer; i++) { + ent = g_entities + i; + if ((ent != NULL) && (ent->inuse)) { + loc = G_Client_GetLocation( ent ); + if (loc != NULL) { + ent->client->pers.teamState.location = loc->health; + } else { + ent->client->pers.teamState.location = 0; + } + } + } + + for (i = 0; i < g_maxclients.integer; i++) { + ent = g_entities + i; + if ((ent != NULL) && (ent->inuse)) { + G_Client_LocationsMessage( ent ); + } + } + + G_Client_CheckHealthInfoMessage(); + } +} + +/* +============ +G_Client_AddScore + +Adds score to both the client and his team +============ +*/ +void G_Client_AddScore( gentity_t *ent, int score ) { + if ( !ent ) + { + return; + } + if ( !ent->client ) { + return; + } + + if(!ent->client->UpdateScore) + { + return; + } + + ent->client->ps.persistant[PERS_SCORE] += score; + //don't add score to team score during elimination + if (g_gametype.integer == GT_TEAM) + {//this isn't capture score + level.teamScores[ ent->client->ps.persistant[PERS_TEAM] ] += score; + } + G_Client_CalculateRanks( qfalse ); + + //RPG-X: RedTechie - Lets enable score updating without this scores will not be updated + ent->client->UpdateScore = qfalse; +} + +/* +============= +SortRanks + +============= +*/ +static int QDECL SortRanks( const void *a, const void *b ) { + gclient_t *ca, *cb; + + ca = &level.clients[*(int *)a]; + cb = &level.clients[*(int *)b]; + + // sort special clients last + if ( ca->sess.spectatorState == SPECTATOR_SCOREBOARD || ca->sess.spectatorClient < 0 ) { + return 1; + } + if ( cb->sess.spectatorState == SPECTATOR_SCOREBOARD || cb->sess.spectatorClient < 0 ) { + return -1; + } + + // then connecting clients + if ( ca->pers.connected == CON_CONNECTING ) { + return 1; + } + if ( cb->pers.connected == CON_CONNECTING ) { + return -1; + } + + + // then spectators + if ( ca->sess.sessionTeam == TEAM_SPECTATOR && cb->sess.sessionTeam == TEAM_SPECTATOR ) { + if ( ca->sess.spectatorTime < cb->sess.spectatorTime ) { + return -1; + } + if ( ca->sess.spectatorTime > cb->sess.spectatorTime ) { + return 1; + } + return 0; + } + if ( ca->sess.sessionTeam == TEAM_SPECTATOR ) { + return 1; + } + if ( cb->sess.sessionTeam == TEAM_SPECTATOR ) { + return -1; + } + + // then sort by score & number of times killed + if ( ca->ps.persistant[PERS_SCORE] + > cb->ps.persistant[PERS_SCORE] ) { + return -1; + } + if ((ca->ps.persistant[PERS_SCORE] == cb->ps.persistant[PERS_SCORE]) && + (ca->ps.persistant[PERS_KILLED] < cb->ps.persistant[PERS_KILLED]) ) + { return -1;} + + if ( ca->ps.persistant[PERS_SCORE] + < cb->ps.persistant[PERS_SCORE] ) { + return 1; + } + if ((ca->ps.persistant[PERS_SCORE] == cb->ps.persistant[PERS_SCORE]) && + (ca->ps.persistant[PERS_KILLED] > cb->ps.persistant[PERS_KILLED]) ) + { return 1;} + + return 0; +} + +/* +============ +G_Client_CalculateRanks + +Recalculates the score ranks of all players +This will be called on every client connect, begin, disconnect, death, +and team change. + +FIXME: for elimination, the last man standing must be ranked first +============ +*/ +void G_Client_CalculateRanks( qboolean fromExit ) { + int32_t i = 0; + + gclient_t *cl; + + level.follow1 = -1; + level.follow2 = -1; + level.numConnectedClients = 0; + level.numNonSpectatorClients = 0; + level.numPlayingClients = 0; + level.numVotingClients = 0; // don't count bots + for ( ; i < level.maxclients ; i++ ) { + if ( level.clients[i].pers.connected != CON_DISCONNECTED ) { + level.sortedClients[level.numConnectedClients] = i; + level.numConnectedClients++; + + if ( level.clients[i].sess.sessionTeam != TEAM_SPECTATOR ) { + level.numNonSpectatorClients++; + + // decide if this should be auto-followed + if ( level.clients[i].pers.connected == CON_CONNECTED ) { + level.numPlayingClients++; + if ( (g_entities[i].r.svFlags & SVF_BOT) == 0 ) { + level.numVotingClients++; + } + if ( level.follow1 == -1 ) { + level.follow1 = i; + } else if ( level.follow2 == -1 ) { + level.follow2 = i; + } + } + } + } + } + + qsort( level.sortedClients, (size_t)level.numConnectedClients, + sizeof(level.sortedClients[0]), SortRanks ); + + // set the rank value for all clients that are connected and not spectators + if ( g_gametype.integer >= GT_TEAM ) { + // in team games, rank is just the order of the teams, 0=red, 1=blue, 2=tied + for ( i = 0; i < level.numConnectedClients; i++ ) { + cl = &level.clients[ level.sortedClients[i] ]; + if ( level.teamScores[TEAM_RED] == level.teamScores[TEAM_BLUE] ) { + cl->ps.persistant[PERS_RANK] = 2; + } else if ( level.teamScores[TEAM_RED] > level.teamScores[TEAM_BLUE] ) { + cl->ps.persistant[PERS_RANK] = 0; + } else { + cl->ps.persistant[PERS_RANK] = 1; + } + } + } else { + int32_t rank = -1; + int32_t score = 0; + for ( i = 0; i < level.numPlayingClients; i++ ) { + int32_t newScore = 0; + + cl = &level.clients[ level.sortedClients[i] ]; + newScore = cl->ps.persistant[PERS_SCORE]; + if ( i == 0 || newScore != score ) { + rank = i; + // assume we aren't tied until the next client is checked + level.clients[ level.sortedClients[i] ].ps.persistant[PERS_RANK] = rank; + } else { + // we are tied with the previous client + level.clients[ level.sortedClients[i-1] ].ps.persistant[PERS_RANK] = rank | RANK_TIED_FLAG; + level.clients[ level.sortedClients[i] ].ps.persistant[PERS_RANK] = rank | RANK_TIED_FLAG; + } + score = newScore; + if ( g_gametype.integer == GT_SINGLE_PLAYER && level.numPlayingClients == 1 ) { + level.clients[ level.sortedClients[i] ].ps.persistant[PERS_RANK] = rank | RANK_TIED_FLAG; + } + } + } + + // set the CS_SCORES1/2 configstrings, which will be visible to everyone + if ( g_gametype.integer >= GT_TEAM ) { + trap_SetConfigstring( CS_SCORES1, va("%i", level.teamScores[TEAM_RED] ) ); + trap_SetConfigstring( CS_SCORES2, va("%i", level.teamScores[TEAM_BLUE] ) ); + } else { + if ( level.numConnectedClients == 0 ) { + trap_SetConfigstring( CS_SCORES1, va("%i", SCORE_NOT_PRESENT) ); + trap_SetConfigstring( CS_SCORES2, va("%i", SCORE_NOT_PRESENT) ); + } else if ( level.numConnectedClients == 1 ) { + trap_SetConfigstring( CS_SCORES1, va("%i", level.clients[ level.sortedClients[0] ].ps.persistant[PERS_SCORE] ) ); + trap_SetConfigstring( CS_SCORES2, va("%i", SCORE_NOT_PRESENT) ); + } else { + trap_SetConfigstring( CS_SCORES1, va("%i", level.clients[ level.sortedClients[0] ].ps.persistant[PERS_SCORE] ) ); + trap_SetConfigstring( CS_SCORES2, va("%i", level.clients[ level.sortedClients[1] ].ps.persistant[PERS_SCORE] ) ); + } + } + + // if we are at the intermission, send the new info to everyone + if ( level.intermissiontime != 0 ) { + SendScoreboardMessageToAllClients(); + } +} + +void G_Client_UpdateSoundZones(void) { + int32_t i = 0; + int32_t b = 0; + int32_t entlist[MAX_GENTITIES]; + int32_t zones[MAX_CLIENTS]; + int32_t count = 0; + char supdate[MAX_STRING_CHARS]; + + memset(&zones, 0, sizeof(zones)); + memset(&supdate, 0, sizeof(supdate)); + + for( ; i < MAX_GENTITIES; i++) { + if((g_entities[i].type == ENT_TARGET_ZONE) && (g_entities[i].count == 3)) { + memset(&entlist, 0, sizeof(entlist)); + count = trap_EntitiesInBox(g_entities[i].r.mins, g_entities[i].r.maxs, (int32_t*)&entlist, MAX_GENTITIES); + + for(b = 0; b < count; b++) { + if(g_entities[entlist[b]].client != NULL) { + zones[g_entities[entlist[b]].client->ps.clientNum] = g_entities[i].s.number; + } + } + } + } + + for(i = 0; i < MAX_CLIENTS; i++) { + if(strlen(supdate) != 0) { + Com_sprintf(supdate, sizeof(supdate), "%s\\c%d\\%d", supdate, i, zones[i]); + } else { + Com_sprintf(supdate, sizeof(supdate), "c%d\\%d", i, zones[i]); + } + } + + for(i = 0; i < MAX_GENTITIES; i++) { + if(g_entities[i].client != NULL) { + trap_SendServerCommand(i, va("slup %s", supdate)); + } + } +} + +void G_Client_SetScore(gentity_t* ent, int32_t score) { + + G_Assert(ent, (void)0); + G_Assert(ent->client, (void)0); + + if (!ent->client->UpdateScore) { + return; + } + + ent->client->ps.persistant[PERS_SCORE] = score; + G_Client_CalculateRanks(qfalse); + + // TiM: send the current scoring to all clients + SendScoreboardMessageToAllClients(); + + //RPG-X: RedTechie - Lets enable score updating without this scores will not be updated + ent->client->UpdateScore = qfalse; +} + +void G_Client_TossClientItems(gentity_t* self, qboolean dis_con) { + double angle = 0; + int32_t i = 0; + int32_t times = 0; + gentity_t* drop = NULL; + playerState_t *ps = NULL; + gitem_t* item = NULL; + + G_Assert(self, (void)0); + G_Assert(self->client, (void)0); + + ps = &self->client->ps; + + if ((self->flags & FL_CLOAK) != 0) { + // remove the invisible powerup if the player is cloaked. + //RPG-X: RedTechie - Also remove ghost + ps->powerups[PW_GHOST] = level.time; + ps->powerups[PW_INVIS] = level.time; + } + + if ((self->flags & FL_FLY) != 0) { + // remove the flying powerup if the player is flying. + ps->powerups[PW_FLIGHT] = level.time; + } + + //RPG-X | Phenix | 8/8/2004 + if ((self->flags & FL_EVOSUIT) != 0) { + // remove the evosuit powerup + ps->powerups[PW_EVOSUIT] = level.time; + } + + // drop all the powerups if not in teamplay + if (g_gametype.integer != GT_TEAM) { + angle = 45; + for (i = 1; i < PW_NUM_POWERUPS; i++) { + if (ps->powerups[i] > level.time) { + item = BG_FindItemForPowerup(i); + if (item == NULL) { + continue; + } + + drop = Drop_Item(self, item, angle); + // decide how many seconds it has left + drop->count = (ps->powerups[i] - level.time) / 1000; + if (drop->count < 1) { + drop->count = 1; + } + angle += 45; + } + } + } + + // RPG-X | Marcin | 30/12/2008 + // ... + if (rpg_allowWeaponDrop.integer == 0 || rpg_dropOnDeath.integer == 0 || dis_con) { + return; + } + + // Drop ALL weapons in inventory + for (i = 0; i < WP_NUM_WEAPONS; ++i) { + // these weapons should not be tossed (hand and null) + if (Max_Weapons[i] == NULL) { + continue; + } + + //RPG-X | GSIO01 | 08/05/2009: let's make sure we only drop weapons the player has + item = NULL; + if (ps->ammo[i]) { + times = ps->ammo[i]; + item = BG_FindItemForWeapon((weapon_t)i); + while (times-- > 0) { // the 'goes towards' operator :p + Drop_Item(self, item, 0); + } + } + } + + // then remove weapons + + for (i = 0; i < WP_NUM_WEAPONS; ++i) { + ps->stats[STAT_WEAPONS] &= ~i; + ps->ammo[i] = 0; + } +} + +void body_die(gentity_t* self, gentity_t* inflictor, gentity_t* attacker, int32_t damage, int32_t meansOfDeath) { + int32_t contents = 0; + + G_Assert(self, (void)0); + + contents = trap_PointContents(self->r.currentOrigin, -1); + if (rpg_medicsrevive.integer == 1 && !(contents & CONTENTS_NODROP) && (meansOfDeath != MOD_TRIGGER_HURT)) { + if (self->health > GIB_HEALTH_IMPOSSIBLE) { + return; + } + + } else { + if (self->health > GIB_HEALTH) { + return; + } + } + G_Combat_GibEntity(self, 0); +} + +char* G_Client_ClassNameForValue(pclass_t pClass) { + static char buffer[MAX_QPATH]; + char *ptr = NULL; + + trap_Cvar_VariableStringBuffer(va("rpg_%sPass", g_classData[pClass].consoleName), buffer, sizeof(buffer)); + + ptr = buffer; + + return ptr; +} + +// these are just for logging, the client prints its own messages +char* modNames[MOD_MAX] = { + "MOD_UNKNOWN", + + "MOD_WATER", + "MOD_SLIME", + "MOD_LAVA", + "MOD_CRUSH", + "MOD_TELEFRAG", + "MOD_FALLING", + "MOD_SUICIDE", + "MOD_TARGET_LASER", + "MOD_TRIGGER_HURT", + + // Trek weapons + "MOD_PHASER", + "MOD_PHASER_ALT", + "MOD_CRIFLE", + "MOD_CRIFLE_SPLASH", + "MOD_CRIFLE_ALT", + "MOD_CRIFLE_ALT_SPLASH", + "MOD_IMOD", + "MOD_IMOD_ALT", + "MOD_SCAVENGER", + "MOD_SCAVENGER_ALT", + "MOD_SCAVENGER_ALT_SPLASH", + "MOD_STASIS", + "MOD_STASIS_ALT", + "MOD_GRENADE", + "MOD_GRENADE_ALT", + "MOD_GRENADE_SPLASH", + "MOD_GRENADE_ALT_SPLASH", + "MOD_TETRYON", + "MOD_TETRYON_ALT", + "MOD_DREADNOUGHT", + "MOD_DREADNOUGHT_ALT", + "MOD_QUANTUM", + "MOD_QUANTUM_SPLASH", + "MOD_QUANTUM_ALT", + "MOD_QUANTUM_ALT_SPLASH", + + "MOD_DETPACK", + "MOD_SEEKER" + + //expansion pack + "MOD_KNOCKOUT", + "MOD_ASSIMILATE", + "MOD_BORG", + "MOD_BORG_ALT", + + "MOD_RESPAWN", + "MOD_EXPLOSION", +}; //must be kept up to date with bg_public, meansOfDeath_t + +/* +================== +G_Client_Die +Heavly Modifyed By: RedTechie +RPG-X: Marcin: a little bit modified - 30/12/2008 +================== +*/ +void G_Client_Die(gentity_t* self, gentity_t* inflictor, gentity_t* attacker, int32_t damage, int32_t meansOfDeath) { + //--------------------- + //RPG-X: RedTechie - Check to see if medics revive people and not respawn if true use my fake death insead :) + //--------------------- + int32_t contents = 0; + + G_Assert(self, (void)0); + G_Assert(self->client, (void)0); + + //RPG-X: RedTechie - Make sure there not getting killed by a trigger kill or the medics wont be able to heal them + contents = trap_PointContents(self->r.currentOrigin, -1); + + if (rpg_medicsrevive.integer == 1 && !(contents & CONTENTS_NODROP) && (meansOfDeath != MOD_TRIGGER_HURT)) { + char* classname = NULL; + char* killerName = NULL; + char* obit = NULL; + int32_t anim = 0; + int32_t killer = 0; + int32_t i = 0; + gentity_t* detpack = NULL; + gentity_t* ent = NULL; + playerState_t* ps = &self->client->ps; + + //RPG-X: RedTechie - Blow up a detpack if some one placed it and died + classname = BG_FindClassnameForHoldable(HI_DETPACK); + if (classname != NULL) { + while ((detpack = G_Find(detpack, FOFS(classname), classname)) != NULL) { + if (detpack->parent == self) { + detpack->think = DetonateDetpack; // Detonate next think. + detpack->nextthink = level.time; + } + } + } + + + //RPG-X: Redtechie - Do some score keeping witch we commented out and log + if (attacker != NULL) { + killer = attacker->s.number; + if (attacker->client != NULL) { + killerName = attacker->client->pers.netname; + } else { + killerName = ""; + } + } else { + killer = ENTITYNUM_WORLD; + killerName = ""; + } + + if (killer < 0 || killer >= MAX_CLIENTS) { + killer = ENTITYNUM_WORLD; + killerName = ""; + } + + if (meansOfDeath < 0 || meansOfDeath >= sizeof(modNames) / sizeof(modNames[0])) { + obit = ""; + } else { + obit = modNames[meansOfDeath]; + } + + G_LogPrintf("Kill: %i %i %i: %s killed %s by %s\n", killer, self->s.number, meansOfDeath, killerName, self->client->pers.netname, obit); + + G_LogWeaponKill(killer, meansOfDeath); + G_LogWeaponDeath(self->s.number, self->s.weapon); + if (attacker != NULL && attacker->client != NULL && attacker->inuse) { + G_LogWeaponFrag(killer, self->s.number); + } + + if (meansOfDeath != MOD_RESPAWN && meansOfDeath != MOD_CUSTOM_DIE) { + // broadcast the death event to everyone + ent = G_TempEntity(self->r.currentOrigin, EV_OBITUARY); + ent->s.eventParm = meansOfDeath; + ent->s.otherEntityNum = self->s.number; + ent->s.otherEntityNum2 = killer; + ent->r.svFlags = SVF_BROADCAST; // send to everyone + } + + self->enemy = attacker; + + ps->persistant[PERS_KILLED]++; + if (self == attacker) { + self->client->pers.teamState.suicides++; + } else { + //RPG-X | Phenix | 06/04/2005 + // N00b Protection, you kill two people and puff your auto n00b! + + if (attacker != NULL) { + if (attacker->client != NULL) { + if (G_Client_IsAdmin(attacker) == qfalse) { + attacker->n00bCount++; + + attacker->client->fraggerTime = level.time + (rpg_fraggerSpawnDelay.integer * 1000); + + if (rpg_kickAfterXkills.integer < 1) { + trap_SendServerCommand(attacker - g_entities, va("print \"^7Server: You have been caught n00bing, you have been temporary put in the n00b class.\n\"")); + } else { + trap_SendServerCommand(attacker - g_entities, va("print \"^7Server: You have been caught n00bing, %i more times and you will be kicked.\n\"", (rpg_kickAfterXkills.integer - attacker->n00bCount))); + } + + if ((attacker->n00bCount >= rpg_kickAfterXkills.integer) && (rpg_kickAfterXkills.integer != 0)) { + trap_DropClient(attacker->s.number, "Kicked: Do Not N00b!"); + } else { + for (i = 0; g_classData[i].consoleName[0] && i < MAX_CLASSES; i++) { + if (g_classData[i].isn00b) { + char conName[64]; + trap_Cvar_VariableStringBuffer(va("rpg_%sPass", conName), conName, sizeof(conName)); + + Q_strncpyz(attacker->client->origClass, G_Client_ClassNameForValue(attacker->client->sess.sessionClass), sizeof(attacker->client->origClass)); + attacker->client->n00bTime = level.time + 10000; + SetClass(attacker, conName, NULL, qfalse); + break; + } + } + } + } + } + } + } + + //RPG-X: RedTechie no noclip + if (self->client->noclip) { + self->client->noclip = qfalse; + } + + //RPG-X: RedTechie - Toss items + //RPG-X: Marcin - not when respawning - 30/12/2008 + if (meansOfDeath != MOD_RESPAWN) { + G_Client_TossClientItems(self, qfalse); + } + + ps->pm_type = PM_DEAD; + + self->takedamage = qfalse; + + ps->weapon = WP_0; + ps->weaponstate = WEAPON_READY; + self->r.contents = CONTENTS_CORPSE; + + //-TiM + + self->s.loopSound = 0; + + self->r.maxs[2] = -8; + + //RPG-X: RedTechie - Wait....forever + self->client->respawnTime = level.time + 1000000000; + + //Play death sound + //RPG-X: RedTechie - No pain sound when they change class + if (meansOfDeath != MOD_RESPAWN) { + G_AddEvent(self, irandom(EV_DEATH1, EV_DEATH3), killer); + //if we died from falling, add a nice "splat' sound lol + if (meansOfDeath == MOD_FALLING) { + G_AddEvent(self, EV_SPLAT, killer); + } + } + + //RPG-X : Model system - Death animations now based on vector hit + if (meansOfDeath == MOD_FALLING) { + anim = BOTH_FALLDEATH1LAND; + } else if (self->waterlevel == 3) { + anim = BOTH_FLOAT2; + } else { + if (meansOfDeath == MOD_PHASER || meansOfDeath == MOD_PHASER_ALT) { + if ((self->client->lasthurt_location & LOCATION_FRONT) != 0) { + anim = BOTH_DEATHBACKWARD1; + } else if ((self->client->lasthurt_location & LOCATION_BACK) != 0) { + anim = BOTH_DEATHFORWARD2; + } else if ((self->client->lasthurt_location & LOCATION_LEFT) != 0) { + anim = BOTH_DEATH2; + } else if ((self->client->lasthurt_location & LOCATION_RIGHT) != 0) { + anim = BOTH_DEATH2; + } else { + anim = BOTH_DEATH1; + } + } else { + if ((self->client->lasthurt_location & LOCATION_FRONT) != 0) { + anim = BOTH_DEATHBACKWARD2; + } else if ((self->client->lasthurt_location & LOCATION_BACK) != 0) { + anim = BOTH_DEATHFORWARD1; + } else if ((self->client->lasthurt_location & LOCATION_LEFT) != 0) { + anim = BOTH_DEATHFORWARD2; + } else if ((self->client->lasthurt_location & LOCATION_RIGHT) != 0) { + anim = BOTH_DEATHFORWARD2; + } else { + anim = BOTH_DEATH1; + } + } + } + + //TiM + ps->stats[LEGSANIM] = ((ps->stats[LEGSANIM] & ANIM_TOGGLEBIT) ^ ANIM_TOGGLEBIT) | anim; + ps->stats[TORSOANIM] = ((ps->stats[TORSOANIM] & ANIM_TOGGLEBIT) ^ ANIM_TOGGLEBIT) | anim; + + trap_LinkEntity(self); + + BG_PlayerStateToEntityState(&self->client->ps, &self->s, qtrue); + + G_Client_UserinfoChanged(self->s.clientNum); + + ClientEndFrame(self); + + G_Client_StoreClientInitialStatus(self); + //--------------------- + //RPG-X: RedTechie - If it dose equal 0 use regular die + //--------------------- + } else { + char* killerName = NULL; + char* obit = NULL; + char* classname = NULL; + int anim = 0; + int killer = 0; + int i = 0; + int BottomlessPitDeath = 0; + static int deathNum; + gentity_t* ent = NULL; + gentity_t* detpack = NULL; + playerState_t* ps = &self->client->ps; + + if (ps->pm_type == PM_DEAD) { + return; + } + + if (level.intermissiontime != 0) { + return; + } + + //RPG-X: RedTechie - Trying to make sure player dies when there health is 1 without medics revive turned on + //RPG-X | Phenix | 05/04/2005 - Read learn that "=" sets where "==" is an if statement!!! + if (self->health == 1) { + self->health = 0; + } + + ps->pm_type = PM_DEAD; + //need to copy health here because pm_type was getting reset to PM_NORMAL if ClientThink_real was called before the STAT_HEALTH was updated + ps->stats[STAT_HEALTH] = self->health; + + // check if we are in a NODROP Zone and died from a TRIGGER HURT + // if so, we assume that this resulted from a fall to a "bottomless pit" and + // treat it differently... + // + // Any problems with other places in the code? + // + BottomlessPitDeath = 0; // initialize + + contents = trap_PointContents(self->r.currentOrigin, -1); + if ((contents & CONTENTS_NODROP) != 0 && (meansOfDeath == MOD_TRIGGER_HURT)) { + BottomlessPitDeath = 1; + } + + // similarly, if El Corpso here has already dropped a detpack, blow it up + classname = BG_FindClassnameForHoldable(HI_DETPACK); + if (classname) { + while ((detpack = G_Find(detpack, FOFS(classname), classname)) != NULL) { + if (detpack->parent == self) { + detpack->think = DetonateDetpack; // Detonate next think. + detpack->nextthink = level.time; + } + } + } + + if (attacker != NULL) { + killer = attacker->s.number; + if (attacker->client != NULL) { + killerName = attacker->client->pers.netname; + } else { + killerName = ""; + } + } else { + killer = ENTITYNUM_WORLD; + killerName = ""; + } + + if (killer < 0 || killer >= MAX_CLIENTS) { + killer = ENTITYNUM_WORLD; + killerName = ""; + } + + if (meansOfDeath < 0 || meansOfDeath >= sizeof(modNames) / sizeof(modNames[0])) { + obit = ""; + } else { + obit = modNames[meansOfDeath]; + } + + G_LogPrintf("Kill: %i %i %i: %s killed %s by %s\n", killer, self->s.number, meansOfDeath, killerName, self->client->pers.netname, obit); + + G_LogWeaponKill(killer, meansOfDeath); + G_LogWeaponDeath(self->s.number, self->s.weapon); + if (attacker != NULL && attacker->client != NULL && attacker->inuse) { + G_LogWeaponFrag(killer, self->s.number); + } + + if (meansOfDeath != MOD_RESPAWN) { + // broadcast the death event to everyone + ent = G_TempEntity(self->r.currentOrigin, EV_OBITUARY); + ent->s.eventParm = meansOfDeath; + ent->s.otherEntityNum = self->s.number; + ent->s.otherEntityNum2 = killer; + ent->r.svFlags = SVF_BROADCAST; // send to everyone + } + + self->enemy = attacker; + + ps->persistant[PERS_KILLED]++; + if (self == attacker) { + self->client->pers.teamState.suicides++; + } + + //RPG-X: Redtechie - No awards or score calculations + //////////////////////////////////////////////////////////////////////// + if (attacker != NULL && attacker->client != NULL) { + if (attacker == self) { + if (meansOfDeath != MOD_RESPAWN) {//just changing class + G_Client_AddScore(attacker, -1); + } + } else { + attacker->client->pers.teamState.frags++; + G_Client_AddScore(attacker, 1); + + // Check to see if the player is on a streak. + attacker->client->streakCount++; + + attacker->client->lastKillTime = level.time; + } + } else { + if (meansOfDeath != MOD_RESPAWN) {//not just changing class + G_Client_AddScore(self, -1); + } + } + //////////////////////////////////////////////////////////////////////// + + //RPG-X: Redtechie - agian no need + // Add team bonuses + //Team_FragBonuses(self, inflictor, attacker); + + // if client is in a nodrop area, don't drop anything (but return CTF flags!) + if ((contents & CONTENTS_NODROP) == 0 && meansOfDeath != MOD_SUICIDE && meansOfDeath != MOD_RESPAWN) {//action hero doesn't drop stuff + //don't drop stuff in specialty mode + if (meansOfDeath != MOD_RESPAWN) { + G_Client_TossClientItems(self, qfalse); + } + } + + DeathmatchScoreboardMessage(self); // show scores + // send updated scores to any clients that are following this one, + // or they would get stale scoreboards + for (i = 0; i < level.maxclients; i++) { + gclient_t* client = NULL; + + client = &level.clients[i]; + if (client == NULL) { + continue; + } + + if (client->pers.connected != CON_CONNECTED) { + continue; + } + if (client->sess.sessionTeam != TEAM_SPECTATOR) { + continue; + } + if (client->sess.spectatorClient == self->s.number) { + DeathmatchScoreboardMessage(g_entities + i); + } + } + + self->takedamage = qtrue; // can still be gibbed + + self->s.weapon = WP_0; + self->s.powerups = 0; + self->r.contents = CONTENTS_CORPSE; + + self->s.loopSound = 0; + + self->r.maxs[2] = -8; + + // don't allow respawn until the death anim is done + // g_forcerespawn may force spawning at some later time + self->client->respawnTime = level.time + 1700; + + // We always want to see the body for special animations, so make sure not to gib right away: + if (meansOfDeath == MOD_CRIFLE_ALT || + meansOfDeath == MOD_DETPACK || + meansOfDeath == MOD_QUANTUM_ALT || + meansOfDeath == MOD_DREADNOUGHT_ALT || + meansOfDeath == MOD_PHASER_ALT)//RPG-X: RedTechie - Added phaser alt disnt + { + self->health = 0; + } + + //RPG-X : Model system - Death animations now based on vector hit + if (meansOfDeath == MOD_FALLING) { + anim = BOTH_FALLDEATH1LAND; + } else if (self->waterlevel == 3) { + anim = BOTH_FLOAT2; + } else { + if (meansOfDeath == MOD_PHASER || meansOfDeath == MOD_PHASER_ALT) { + if ((self->client->lasthurt_location & LOCATION_FRONT) != 0) { + anim = BOTH_DEATHBACKWARD1; + } else if ((self->client->lasthurt_location & LOCATION_BACK) != 0) { + anim = BOTH_DEATHFORWARD2; + } else if ((self->client->lasthurt_location & LOCATION_LEFT) != 0) { + anim = BOTH_DEATH2; + } else if ((self->client->lasthurt_location & LOCATION_RIGHT) != 0) { + anim = BOTH_DEATH2; + } else { + anim = BOTH_DEATH1; + } + } else { + if ((self->client->lasthurt_location & LOCATION_FRONT) != 0) { + anim = BOTH_DEATHBACKWARD2; + } else if ((self->client->lasthurt_location & LOCATION_BACK) != 0) { + anim = BOTH_DEATHFORWARD1; + } else if ((self->client->lasthurt_location & LOCATION_LEFT) != 0) { + anim = BOTH_DEATHFORWARD2; + } else if ((self->client->lasthurt_location & LOCATION_RIGHT) != 0) { + anim = BOTH_DEATHFORWARD2; + } else { + anim = BOTH_DEATH1; + } + } + } + + ps->stats[LEGSANIM] = ((ps->stats[LEGSANIM] & ANIM_TOGGLEBIT) ^ ANIM_TOGGLEBIT) | anim; + ps->stats[TORSOANIM] = ((ps->stats[TORSOANIM] & ANIM_TOGGLEBIT) ^ ANIM_TOGGLEBIT) | anim; + + if ((BottomlessPitDeath == 1) && (killer == ENTITYNUM_WORLD)) { + //G_AddEvent( self, EV_FALL_FAR, killer ); ?? Need to play falling SF now, or + // use designer trigger?? + //FIXME: need *some* kind of death anim! + } else { + // normal death + + switch (meansOfDeath) { + case MOD_PHASER_ALT: //RPG-X: RedTechie - Added better effect for alt phaser + if (rpg_phaserdisintegrates.integer == 1) {//RPG-X: RedTechie - Check to see if we want this + G_AddEvent(self, EV_DISINTEGRATION, killer); + ps->powerups[PW_DISINTEGRATE] = level.time + 100000; + VectorClear(ps->velocity); + self->takedamage = qfalse; + self->r.contents = 0; + } + break; + case MOD_CRIFLE_ALT: + break; + case MOD_QUANTUM_ALT: + G_AddEvent(self, EV_DISINTEGRATION2, killer); + ps->powerups[PW_EXPLODE] = level.time + 100000; + VectorClear(ps->velocity); + self->takedamage = qfalse; + self->r.contents = 0; + break; + case MOD_SCAVENGER_ALT: + case MOD_SCAVENGER_ALT_SPLASH: + case MOD_GRENADE: + case MOD_GRENADE_ALT: + case MOD_GRENADE_SPLASH: + case MOD_GRENADE_ALT_SPLASH: + case MOD_QUANTUM: + case MOD_QUANTUM_SPLASH: + case MOD_QUANTUM_ALT_SPLASH: + case MOD_DETPACK: + G_AddEvent(self, EV_EXPLODESHELL, killer); + ps->powerups[PW_EXPLODE] = level.time + 100000; + VectorClear(ps->velocity); + self->takedamage = qfalse; + self->r.contents = 0; + break; + case MOD_DREADNOUGHT: + case MOD_DREADNOUGHT_ALT: + G_AddEvent(self, EV_ARCWELD_DISINT, killer); + ps->powerups[PW_ARCWELD_DISINT] = level.time + 100000; + VectorClear(ps->velocity); + self->takedamage = qfalse; + self->r.contents = 0; + break; + case MOD_FALLING: + G_AddEvent(self, EV_SPLAT, killer); + break; + default: + G_AddEvent(self, irandom(EV_DEATH1, EV_DEATH3), killer); + break; + } + + // the body can still be gibbed + self->die = body_die; + + } + // globally cycle through the different death animations + deathNum = (deathNum + 1) % 3; + + trap_LinkEntity(self); + }//RPG-X: RedTechie - End of my if statment for medics revive check +}//RPG-X: RedTechie - End of void + +qboolean G_Client_IsBorg(gentity_t* ent) { + if (ent == NULL) { + return qfalse; + } + + if (ent->client == NULL) { + return qfalse; + } + + if (g_classData[ent->client->sess.sessionClass].isBorg) { + return qtrue; + } else { + return qfalse; + } +} + + +