From 425decdf7e9284d15aa726e3ae96b9942fb0e3ea Mon Sep 17 00:00:00 2001 From: IronClawTrem Date: Sun, 16 Feb 2020 03:40:06 +0000 Subject: create tremded branch --- src/game/g_client.c | 1428 +++++++++++++++++---------------------------------- 1 file changed, 467 insertions(+), 961 deletions(-) (limited to 'src/game/g_client.c') diff --git a/src/game/g_client.c b/src/game/g_client.c index c77d4f7..3481af9 100644 --- a/src/game/g_client.c +++ b/src/game/g_client.c @@ -1,13 +1,14 @@ /* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, +published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous is distributed in the hope that it will be @@ -16,8 +17,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +along with Tremulous; if not, see + =========================================================================== */ @@ -79,97 +80,6 @@ void SP_info_human_intermission( gentity_t *ent ) { } -/* -=============== -G_OverflowCredits -=============== -*/ -void G_OverflowCredits( gclient_t *doner, int credits ) -{ - int i; - int maxCredits; - int clientNum; - - if( !g_creditOverflow.integer ) - return; - - if( doner->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) - { - maxCredits = ALIEN_MAX_KILLS; - clientNum = level.lastCreditedAlien; - } - else if( doner->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) - { - maxCredits = HUMAN_MAX_CREDITS; - clientNum = level.lastCreditedHuman; - } - else - { - return; - } - - if( g_creditOverflow.integer == 1 ) - { - // distribute to everyone on team - gentity_t *vic; - - i = 0; - while( credits > 0 && i < level.maxclients ) - { - i++; - clientNum++; - if( clientNum >= level.maxclients ) - clientNum = 0; - - vic = &g_entities[ clientNum ]; - if( vic->client->ps.stats[ STAT_PTEAM ] != doner->ps.stats[ STAT_PTEAM ] || - vic->client->ps.persistant[ PERS_CREDIT ] >= maxCredits ) - continue; - - if( vic->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) - level.lastCreditedAlien = clientNum; - else - level.lastCreditedHuman = clientNum; - - if( vic->client->ps.persistant[ PERS_CREDIT ] + credits > maxCredits ) - { - credits -= maxCredits - vic->client->ps.persistant[ PERS_CREDIT ]; - vic->client->ps.persistant[ PERS_CREDIT ] = maxCredits; - } - else - { - vic->client->ps.persistant[ PERS_CREDIT ] += credits; - return; - } - } - } - else if( g_creditOverflow.integer == 2 ) - { - // distribute by team rank - gclient_t *cl; - - for( i = 0; i < level.numPlayingClients && credits > 0; i++ ) - { - // get the client list sorted by rank - cl = &level.clients[ level.sortedClients[ i ] ]; - if( cl->ps.stats[ STAT_PTEAM ] != doner->ps.stats[ STAT_PTEAM ] || - cl->ps.persistant[ PERS_CREDIT ] >= maxCredits ) - continue; - - if( cl->ps.persistant[ PERS_CREDIT ] + credits > maxCredits ) - { - credits -= maxCredits - cl->ps.persistant[ PERS_CREDIT ]; - cl->ps.persistant[ PERS_CREDIT ] = maxCredits; - } - else - { - cl->ps.persistant[ PERS_CREDIT ] += credits; - return; - } - } - } -} - /* =============== G_AddCreditToClient @@ -177,60 +87,30 @@ G_AddCreditToClient */ void G_AddCreditToClient( gclient_t *client, short credit, qboolean cap ) { + int capAmount; + if( !client ) return; - //if we're already at the max and trying to add credit then stop - if( cap ) - { - if( client->pers.teamSelection == PTE_ALIENS ) - { - if( client->pers.credit >= ALIEN_MAX_KILLS && - credit > 0 ) - { - G_OverflowCredits( client, credit ); - return; - } - } - else if( client->pers.teamSelection == PTE_HUMANS ) - { - if( client->pers.credit >= HUMAN_MAX_CREDITS && - credit > 0 ) - { - G_OverflowCredits( client, credit ); - return; - } - } - } - - client->pers.credit += credit; - - if( cap ) + if( cap && credit > 0 ) { - if( client->pers.teamSelection == PTE_ALIENS ) + capAmount = client->pers.teamSelection == TEAM_ALIENS ? + ALIEN_MAX_CREDITS : HUMAN_MAX_CREDITS; + if( client->pers.credit < capAmount ) { - if( client->pers.credit > ALIEN_MAX_KILLS ) - { - G_OverflowCredits( client, client->ps.persistant[ PERS_CREDIT ] - ALIEN_MAX_KILLS ); - client->pers.credit = ALIEN_MAX_KILLS; - } - } - else if( client->pers.teamSelection == PTE_HUMANS ) - { - if( client->pers.credit > HUMAN_MAX_CREDITS ) - { - G_OverflowCredits( client, client->ps.persistant[ PERS_CREDIT ] - HUMAN_MAX_CREDITS ); - client->pers.credit = HUMAN_MAX_CREDITS; - } + client->pers.credit += credit; + if( client->pers.credit > capAmount ) + client->pers.credit = capAmount; } } + else + client->pers.credit += credit; if( client->pers.credit < 0 ) client->pers.credit = 0; - // keep PERS_CREDIT in sync if not following - if( client->sess.spectatorState != SPECTATOR_FOLLOW ) - client->ps.persistant[ PERS_CREDIT ] = client->pers.credit; + // Copy to ps so the client can access it + client->ps.persistant[ PERS_CREDIT ] = client->pers.credit; } @@ -255,8 +135,8 @@ qboolean SpotWouldTelefrag( gentity_t *spot ) gentity_t *hit; vec3_t mins, maxs; - VectorAdd( spot->s.origin, playerMins, mins ); - VectorAdd( spot->s.origin, playerMaxs, maxs ); + VectorAdd( spot->r.currentOrigin, playerMins, mins ); + VectorAdd( spot->r.currentOrigin, playerMaxs, maxs ); num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); for( i = 0; i < num; i++ ) @@ -270,75 +150,6 @@ qboolean SpotWouldTelefrag( gentity_t *spot ) return qfalse; } -/* -================ -G_SelectNearestDeathmatchSpawnPoint - -Find the spot that we DON'T want to use -================ -*/ -#define MAX_SPAWN_POINTS 128 -gentity_t *G_SelectNearestDeathmatchSpawnPoint( vec3_t from ) -{ - gentity_t *spot; - vec3_t delta; - float dist, nearestDist; - gentity_t *nearestSpot; - - nearestDist = 999999; - nearestSpot = NULL; - spot = 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; -} - - -/* -================ -G_SelectRandomDeathmatchSpawnPoint - -go to a random point that doesn't telefrag -================ -*/ -#define MAX_SPAWN_POINTS 128 -gentity_t *G_SelectRandomDeathmatchSpawnPoint( void ) -{ - gentity_t *spot; - int count; - int selection; - gentity_t *spots[ MAX_SPAWN_POINTS ]; - - count = 0; - spot = NULL; - - while( ( spot = G_Find( spot, FOFS( classname ), "info_player_deathmatch" ) ) != NULL ) - { - if( SpotWouldTelefrag( spot ) ) - continue; - - spots[ count ] = spot; - count++; - } - - if( !count ) // no spots that won't telefrag - return G_Find( NULL, FOFS( classname ), "info_player_deathmatch" ); - - selection = rand( ) % count; - return spots[ selection ]; -} - /* =========== @@ -347,7 +158,7 @@ G_SelectRandomFurthestSpawnPoint Chooses a player start, deathmatch start, etc ============ */ -gentity_t *G_SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) +static gentity_t *G_SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) { gentity_t *spot; vec3_t delta; @@ -364,7 +175,7 @@ gentity_t *G_SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin, if( SpotWouldTelefrag( spot ) ) continue; - VectorSubtract( spot->s.origin, avoidPoint, delta ); + VectorSubtract( spot->r.currentOrigin, avoidPoint, delta ); dist = VectorLength( delta ); for( i = 0; i < numSpots; i++ ) @@ -406,18 +217,18 @@ gentity_t *G_SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin, if( !spot ) G_Error( "Couldn't find a spawn point" ); - VectorCopy( spot->s.origin, origin ); + VectorCopy( spot->r.currentOrigin, origin ); origin[ 2 ] += 9; - VectorCopy( spot->s.angles, angles ); + VectorCopy( spot->r.currentAngles, angles ); return spot; } // select a random spot from the spawn points furthest away rnd = random( ) * ( numSpots / 2 ); - VectorCopy( list_spot[ rnd ]->s.origin, origin ); + VectorCopy( list_spot[ rnd ]->r.currentOrigin, origin ); origin[ 2 ] += 9; - VectorCopy( list_spot[ rnd ]->s.angles, angles ); + VectorCopy( list_spot[ rnd ]->r.currentAngles, angles ); return list_spot[ rnd ]; } @@ -425,102 +236,45 @@ gentity_t *G_SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin, /* ================ -G_SelectAlienSpawnPoint - -go to a random point that doesn't telefrag -================ -*/ -gentity_t *G_SelectAlienSpawnPoint( vec3_t preference ) -{ - gentity_t *spot; - int count; - gentity_t *spots[ MAX_SPAWN_POINTS ]; - - if( level.numAlienSpawns <= 0 ) - return NULL; - - count = 0; - spot = NULL; - - while( ( spot = G_Find( spot, FOFS( classname ), - BG_FindEntityNameForBuildable( BA_A_SPAWN ) ) ) != NULL ) - { - if( !spot->spawned ) - continue; - - if( spot->health <= 0 ) - continue; - - if( !spot->s.groundEntityNum ) - continue; - - if( spot->clientSpawnTime > 0 ) - continue; - - if( G_CheckSpawnPoint( spot->s.number, spot->s.origin, - spot->s.origin2, BA_A_SPAWN, NULL ) != NULL ) - continue; - - spots[ count ] = spot; - count++; - } - - if( !count ) - return NULL; - - return G_ClosestEnt( preference, spots, count ); -} - - -/* -================ -G_SelectHumanSpawnPoint +G_SelectSpawnBuildable -go to a random point that doesn't telefrag +find the nearest buildable of the right type that is +spawned/healthy/unblocked etc. ================ */ -gentity_t *G_SelectHumanSpawnPoint( vec3_t preference ) +static gentity_t *G_SelectSpawnBuildable( vec3_t preference, buildable_t buildable ) { - gentity_t *spot; - int count; - gentity_t *spots[ MAX_SPAWN_POINTS ]; - - if( level.numHumanSpawns <= 0 ) - return NULL; + gentity_t *search, *spot; - count = 0; - spot = NULL; + search = spot = NULL; - while( ( spot = G_Find( spot, FOFS( classname ), - BG_FindEntityNameForBuildable( BA_H_SPAWN ) ) ) != NULL ) + while( ( search = G_Find( search, FOFS( classname ), + BG_Buildable( buildable )->entityName ) ) != NULL ) { - if( !spot->spawned ) + if( !search->spawned ) continue; - if( spot->health <= 0 ) + if( search->health <= 0 ) continue; - if( !spot->s.groundEntityNum ) + if( search->s.groundEntityNum == ENTITYNUM_NONE ) continue; - if( spot->clientSpawnTime > 0 ) + if( search->clientSpawnTime > 0 ) continue; - if( G_CheckSpawnPoint( spot->s.number, spot->s.origin, - spot->s.origin2, BA_H_SPAWN, NULL ) != NULL ) + if( G_CheckSpawnPoint( search->s.number, search->r.currentOrigin, + search->s.origin2, buildable, NULL ) != NULL ) continue; - spots[ count ] = spot; - count++; + if( !spot || DistanceSquared( preference, search->r.currentOrigin ) < + DistanceSquared( preference, spot->r.currentOrigin ) ) + spot = search; } - if( !count ) - return NULL; - - return G_ClosestEnt( preference, spots, count ); + return spot; } - /* =========== G_SelectSpawnPoint @@ -541,25 +295,35 @@ G_SelectTremulousSpawnPoint Chooses a player start, deathmatch start, etc ============ */ -gentity_t *G_SelectTremulousSpawnPoint( pTeam_t team, vec3_t preference, vec3_t origin, vec3_t angles ) +gentity_t *G_SelectTremulousSpawnPoint( team_t team, vec3_t preference, vec3_t origin, vec3_t angles ) { gentity_t *spot = NULL; - if( team == PTE_ALIENS ) - spot = G_SelectAlienSpawnPoint( preference ); - else if( team == PTE_HUMANS ) - spot = G_SelectHumanSpawnPoint( preference ); + if( team == TEAM_ALIENS ) + { + if( level.numAlienSpawns <= 0 ) + return NULL; + + spot = G_SelectSpawnBuildable( preference, BA_A_SPAWN ); + } + else if( team == TEAM_HUMANS ) + { + if( level.numHumanSpawns <= 0 ) + return NULL; + + spot = G_SelectSpawnBuildable( preference, BA_H_SPAWN ); + } //no available spots if( !spot ) return NULL; - if( team == PTE_ALIENS ) - G_CheckSpawnPoint( spot->s.number, spot->s.origin, spot->s.origin2, BA_A_SPAWN, origin ); - else if( team == PTE_HUMANS ) - G_CheckSpawnPoint( spot->s.number, spot->s.origin, spot->s.origin2, BA_H_SPAWN, origin ); + if( team == TEAM_ALIENS ) + G_CheckSpawnPoint( spot->s.number, spot->r.currentOrigin, spot->s.origin2, BA_A_SPAWN, origin ); + else if( team == TEAM_HUMANS ) + G_CheckSpawnPoint( spot->s.number, spot->r.currentOrigin, spot->s.origin2, BA_H_SPAWN, origin ); - VectorCopy( spot->s.angles, angles ); + VectorCopy( spot->r.currentAngles, angles ); angles[ ROLL ] = 0; return spot; @@ -567,44 +331,13 @@ gentity_t *G_SelectTremulousSpawnPoint( pTeam_t team, vec3_t preference, vec3_t } -/* -=========== -G_SelectInitialSpawnPoint - -Try to find a spawn point marked 'initial', otherwise -use normal spawn selection. -============ -*/ -gentity_t *G_SelectInitialSpawnPoint( vec3_t origin, vec3_t angles ) -{ - gentity_t *spot; - - spot = NULL; - while( ( spot = G_Find( spot, FOFS( classname ), "info_player_deathmatch" ) ) != NULL ) - { - if( spot->spawnflags & 1 ) - break; - } - - if( !spot || SpotWouldTelefrag( spot ) ) - { - return G_SelectSpawnPoint( vec3_origin, origin, angles ); - } - - VectorCopy( spot->s.origin, origin ); - origin[ 2 ] += 9; - VectorCopy( spot->s.angles, angles ); - - return spot; -} - /* =========== G_SelectSpectatorSpawnPoint ============ */ -gentity_t *G_SelectSpectatorSpawnPoint( vec3_t origin, vec3_t angles ) +static gentity_t *G_SelectSpectatorSpawnPoint( vec3_t origin, vec3_t angles ) { FindIntermissionPoint( ); @@ -633,8 +366,8 @@ gentity_t *G_SelectAlienLockSpawnPoint( vec3_t origin, vec3_t angles ) if( !spot ) return G_SelectSpectatorSpawnPoint( origin, angles ); - VectorCopy( spot->s.origin, origin ); - VectorCopy( spot->s.angles, angles ); + VectorCopy( spot->r.currentOrigin, origin ); + VectorCopy( spot->r.currentAngles, angles ); return spot; } @@ -658,8 +391,8 @@ gentity_t *G_SelectHumanLockSpawnPoint( vec3_t origin, vec3_t angles ) if( !spot ) return G_SelectSpectatorSpawnPoint( origin, angles ); - VectorCopy( spot->s.origin, origin ); - VectorCopy( spot->s.angles, angles ); + VectorCopy( spot->r.currentOrigin, origin ); + VectorCopy( spot->r.currentAngles, angles ); return spot; } @@ -681,7 +414,7 @@ BodySink After sitting around for five seconds, fall into the ground and dissapear ============= */ -void BodySink( gentity_t *ent ) +static void BodySink( gentity_t *ent ) { //run on first BodySink call if( !ent->active ) @@ -704,23 +437,6 @@ void BodySink( gentity_t *ent ) } -/* -============= -BodyFree - -After sitting around for a while the body becomes a freebie -============= -*/ -void BodyFree( gentity_t *ent ) -{ - ent->killedBy = -1; - - //if not claimed in the next minute destroy - ent->think = BodySink; - ent->nextthink = level.time + 60000; -} - - /* ============= SpawnCorpse @@ -729,17 +445,11 @@ A player is respawning, so make an entity that looks just like the existing corpse to leave behind. ============= */ -void SpawnCorpse( gentity_t *ent ) +static void SpawnCorpse( gentity_t *ent ) { gentity_t *body; int contents; - vec3_t origin, dest; - trace_t tr; - float vDiff; - - // prevent crashing everyone with bad corpsenum bug - if( ent->client->pers.connected != CON_CONNECTED ) - return; + vec3_t origin, mins; VectorCopy( ent->r.currentOrigin, origin ); @@ -752,17 +462,18 @@ void SpawnCorpse( gentity_t *ent ) body = G_Spawn( ); - VectorCopy( ent->s.apos.trBase, body->s.angles ); + VectorCopy( ent->s.apos.trBase, body->s.apos.trBase ); + VectorCopy( ent->s.apos.trBase, body->r.currentAngles ); body->s.eFlags = EF_DEAD; body->s.eType = ET_CORPSE; - body->s.number = body - g_entities; body->timestamp = level.time; body->s.event = 0; body->r.contents = CONTENTS_CORPSE; - body->s.clientNum = ent->client->ps.stats[ STAT_PCLASS ]; + body->clipmask = MASK_DEADSOLID; + body->s.clientNum = ent->client->ps.stats[ STAT_CLASS ]; body->nonSegModel = ent->client->ps.persistant[ PERS_STATE ] & PS_NONSEGMODEL; - if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + if( ent->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) body->classname = "humanCorpse"; else body->classname = "alienCorpse"; @@ -815,25 +526,19 @@ void SpawnCorpse( gentity_t *ent ) body->takedamage = qfalse; - body->health = ent->health = ent->client->ps.stats[ STAT_HEALTH ]; - ent->health = 0; + body->health = ent->health; //change body dimensions - BG_FindBBoxForClass( ent->client->ps.stats[ STAT_PCLASS ], NULL, NULL, NULL, body->r.mins, body->r.maxs ); - vDiff = body->r.mins[ 2 ] - ent->r.mins[ 2 ]; + BG_ClassBoundingBox( ent->client->ps.stats[ STAT_CLASS ], mins, NULL, NULL, body->r.mins, body->r.maxs ); //drop down to match the *model* origins of ent and body - VectorSet( dest, origin[ 0 ], origin[ 1 ], origin[ 2 ] - vDiff ); - trap_Trace( &tr, origin, body->r.mins, body->r.maxs, dest, body->s.number, body->clipmask ); - VectorCopy( tr.endpos, origin ); + origin[2] += mins[ 2 ] - body->r.mins[ 2 ]; G_SetOrigin( body, origin ); - VectorCopy( origin, body->s.origin ); body->s.pos.trType = TR_GRAVITY; body->s.pos.trTime = level.time; VectorCopy( ent->client->ps.velocity, body->s.pos.trDelta ); - VectorCopy ( body->s.pos.trBase, body->r.currentOrigin ); trap_LinkEntity( body ); } @@ -846,7 +551,7 @@ G_SetClientViewAngle ================== */ -void G_SetClientViewAngle( gentity_t *ent, vec3_t angle ) +void G_SetClientViewAngle( gentity_t *ent, const vec3_t angle ) { int i; @@ -859,8 +564,9 @@ void G_SetClientViewAngle( gentity_t *ent, vec3_t angle ) 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 ); + VectorCopy( angle, ent->s.apos.trBase ); + VectorCopy( angle, ent->r.currentAngles ); + VectorCopy( angle, ent->client->ps.viewangles ); } /* @@ -870,52 +576,79 @@ respawn */ void respawn( gentity_t *ent ) { + int i; + SpawnCorpse( ent ); - //TA: Clients can't respawn - they must go thru the class cmd + // Clients can't respawn - they must go through the class cmd ent->client->pers.classSelection = PCL_NONE; ClientSpawn( ent, NULL, NULL, NULL ); -} -/* -================ -TeamCount + // stop any following clients that don't have sticky spec on + for( i = 0; i < level.maxclients; i++ ) + { + if( level.clients[ i ].sess.spectatorState == SPECTATOR_FOLLOW && + level.clients[ i ].sess.spectatorClient == ent - g_entities ) + { + if( !( level.clients[ i ].pers.stickySpec ) ) + { + if( !G_FollowNewClient( &g_entities[ i ], 1 ) ) + G_StopFollowing( &g_entities[ i ] ); + } + else + G_FollowLockView( &g_entities[ i ] ); + } + } +} -Returns number of players on a team -================ -*/ -team_t TeamCount( int ignoreClientNum, int team ) +static qboolean G_IsEmoticon( const char *s, qboolean *escaped ) { - int i; - int count = 0; + int i, j; + const char *p = s; + char emoticon[ MAX_EMOTICON_NAME_LEN ] = {""}; + qboolean escape = qfalse; - for( i = 0 ; i < level.maxclients ; i++ ) + if( *p != '[' ) + return qfalse; + p++; + if( *p == '[' ) { - if( i == ignoreClientNum ) - continue; - - if( level.clients[ i ].pers.connected == CON_DISCONNECTED ) - continue; - - if( level.clients[ i ].sess.sessionTeam == team ) - count++; + escape = qtrue; + p++; } - - return count; + i = 0; + while( *p && i < ( MAX_EMOTICON_NAME_LEN - 1 ) ) + { + if( *p == ']' ) + { + for( j = 0; j < level.emoticonCount; j++ ) + { + if( !Q_stricmp( emoticon, level.emoticons[ j ].name ) ) + { + *escaped = escape; + return qtrue; + } + } + return qfalse; + } + emoticon[ i++ ] = *p; + emoticon[ i ] = '\0'; + p++; + } + return qfalse; } - /* =========== -ClientCleanName +G_ClientCleanName ============ */ -static void ClientCleanName( const char *in, char *out, int outSize, qboolean special ) +static void G_ClientCleanName( const char *in, char *out, int outSize ) { int len, colorlessLen; - char ch; char *p; int spaces; + qboolean escaped; qboolean invalid = qfalse; //save room for trailing null byte @@ -927,44 +660,48 @@ static void ClientCleanName( const char *in, char *out, int outSize, qboolean sp *p = 0; spaces = 0; - while( 1 ) + for( ; *in; in++ ) { - ch = *in++; - if( !ch ) - break; - // don't allow leading spaces - if( !*p && ch == ' ' ) + if( colorlessLen == 0 && *in == ' ' ) continue; // don't allow nonprinting characters or (dead) console keys - if( ch < ' ' || ch > '}' || ch == '`' || ch == '%' ) + if( *in < ' ' || *in > '}' || *in == '`' ) continue; // check colors - if( Q_IsColorString( in - 1 ) ) + if( Q_IsColorString( in ) ) { + in++; + // make sure room in dest for both chars if( len > outSize - 2 ) break; - *out++ = ch; - len += 2; + *out++ = Q_COLOR_ESCAPE; - // solo trailing carat is not a color prefix - if( !*in ) { - *out++ = COLOR_WHITE; - break; - } + *out++ = *in; - *out++ = *in; + len += 2; + continue; + } + else if( !g_emoticonsAllowedInNames.integer && G_IsEmoticon( in, &escaped ) ) + { + // make sure room in dest for both chars + if( len > outSize - 2 ) + break; - in++; + *out++ = '['; + *out++ = '['; + len += 2; + if( escaped ) + in++; continue; } // don't allow too many consecutive spaces - if( ch == ' ' ) + if( *in == ' ' ) { spaces++; if( spaces > 3 ) @@ -976,7 +713,7 @@ static void ClientCleanName( const char *in, char *out, int outSize, qboolean sp if( len > outSize - 1 ) break; - *out++ = ch; + *out++ = *in; colorlessLen++; len++; } @@ -984,7 +721,7 @@ static void ClientCleanName( const char *in, char *out, int outSize, qboolean sp *out = 0; // don't allow names beginning with "[skipnotify]" because it messes up /ignore-related code - if( !Q_strncmp( p, "[skipnotify]", 12 ) ) + if( !Q_stricmpn( p, "[skipnotify]", 12 ) ) invalid = qtrue; // don't allow comment-beginning strings because it messes up various parsers @@ -1001,37 +738,6 @@ static void ClientCleanName( const char *in, char *out, int outSize, qboolean sp } -/* -=================== -G_NextNewbieName - -Generate a unique, known-good name for an UnnamedPlayer -=================== -*/ -char *G_NextNewbieName( gentity_t *ent ) -{ - char newname[ MAX_NAME_LENGTH ]; - char namePrefix[ MAX_NAME_LENGTH - 4 ]; - char err[ MAX_STRING_CHARS ]; - - if( g_newbieNamePrefix.string[ 0 ] ) - Q_strncpyz( namePrefix, g_newbieNamePrefix.string , sizeof( namePrefix ) ); - else - strcpy( namePrefix, "Newbie#" ); - - while( level.numNewbies < 10000 ) - { - strcpy( newname, va( "%s%i", namePrefix, level.numNewbies ) ); - if ( G_admin_name_check( ent, newname, err, sizeof( err ) ) ) - { - return va( "%s", newname ); - } - level.numNewbies++; // Only increments if the last requested name was used. - } - return "UnnamedPlayer"; -} - - /* ====================== G_NonSegModel @@ -1099,24 +805,19 @@ The game can override any of the settings and call trap_SetUserinfo if desired. ============ */ -void ClientUserinfoChanged( int clientNum, qboolean forceName ) +char *ClientUserinfoChanged( int clientNum, qboolean forceName ) { gentity_t *ent; - int teamTask, teamLeader, health; - char *s; - char model[ MAX_QPATH ]; - char buffer[ MAX_QPATH ]; + char *s, *s2; + char model[ MAX_QPATH] = { '\0' }; + char buffer[ MAX_QPATH ] = { '\0' }; char filename[ MAX_QPATH ]; char oldname[ MAX_NAME_LENGTH ]; char newname[ MAX_NAME_LENGTH ]; char err[ MAX_STRING_CHARS ]; qboolean revertName = qfalse; - qboolean showRenameMsg = qtrue; gclient_t *client; - char c1[ MAX_INFO_STRING ]; - char c2[ MAX_INFO_STRING ]; char userinfo[ MAX_INFO_STRING ]; - pTeam_t team; ent = g_entities + clientNum; client = ent->client; @@ -1130,81 +831,49 @@ void ClientUserinfoChanged( int clientNum, qboolean forceName ) "disconnect \"illegal or malformed userinfo\n\"" ); trap_DropClient( ent - g_entities, "dropped: illegal or malformed userinfo"); + return "Illegal or malformed userinfo"; } + // If their userinfo overflowed, tremded is in the process of disconnecting them. + // If we send our own disconnect, it won't work, so just return to prevent crashes later + // in this function. This check must come after the Info_Validate call. + else if( !userinfo[ 0 ] ) + return "Empty (overflowed) userinfo"; - - // check for local client - s = Info_ValueForKey( userinfo, "ip" ); - - if( !strcmp( s, "localhost" ) ) - client->pers.localClient = qtrue; - - // check the item prediction - s = Info_ValueForKey( userinfo, "cg_predictItems" ); - - if( !atoi( s ) ) - client->pers.predictItemPickup = qfalse; - else - client->pers.predictItemPickup = qtrue; + // stickyspec toggle + s = Info_ValueForKey( userinfo, "cg_stickySpec" ); + client->pers.stickySpec = atoi( s ) != 0; // set name Q_strncpyz( oldname, client->pers.netname, sizeof( oldname ) ); s = Info_ValueForKey( userinfo, "name" ); - - if ( !G_admin_permission( ent, ADMF_SPECIALNAME ) ) - ClientCleanName( s, newname, sizeof( newname ), qfalse ); - else - ClientCleanName( s, newname, sizeof( newname ), qtrue ); + G_ClientCleanName( s, newname, sizeof( newname ) ); if( strcmp( oldname, newname ) ) { - if( !strlen( oldname ) && client->pers.connected != CON_CONNECTED ) - showRenameMsg = qfalse; - - // in case we need to revert and there's no oldname - if ( !G_admin_permission( ent, ADMF_SPECIALNAME ) ) - ClientCleanName( va( "%s", client->pers.netname ), oldname, sizeof( oldname ), qfalse ); - else - ClientCleanName( va( "%s", client->pers.netname ), oldname, sizeof( oldname ), qtrue ); - - if( g_newbieNumbering.integer ) + if( !forceName && client->pers.namelog->nameChangeTime && + level.time - client->pers.namelog->nameChangeTime <= + g_minNameChangePeriod.value * 1000 ) { - if( !strcmp( newname, "UnnamedPlayer" ) ) - Q_strncpyz( newname, G_NextNewbieName( ent ), sizeof( newname ) ); - if( !strcmp( oldname, "UnnamedPlayer" ) ) - Q_strncpyz( oldname, G_NextNewbieName( ent ), sizeof( oldname ) ); + trap_SendServerCommand( ent - g_entities, va( + "print \"Name change spam protection (g_minNameChangePeriod = %d)\n\"", + g_minNameChangePeriod.integer ) ); + revertName = qtrue; } - - - if( !forceName ) + else if( !forceName && g_maxNameChanges.integer > 0 && + client->pers.namelog->nameChanges >= g_maxNameChanges.integer ) { - if( G_IsMuted( client ) ) - { - trap_SendServerCommand( ent - g_entities, - "print \"You cannot change your name while you are muted\n\"" ); - revertName = qtrue; - } - else if( client->pers.nameChangeTime && - ( level.time - client->pers.nameChangeTime ) - <= ( g_minNameChangePeriod.value * 1000 ) ) - { - trap_SendServerCommand( ent - g_entities, va( - "print \"Name change spam protection (g_minNameChangePeriod = %d)\n\"", - g_minNameChangePeriod.integer ) ); - revertName = qtrue; - } - else if( g_maxNameChanges.integer > 0 - && client->pers.nameChanges >= g_maxNameChanges.integer - && !G_admin_permission( ent, ADMF_SPECIAL ) ) - { - trap_SendServerCommand( ent - g_entities, va( - "print \"Maximum name changes reached (g_maxNameChanges = %d)\n\"", - g_maxNameChanges.integer ) ); - revertName = qtrue; - } + trap_SendServerCommand( ent - g_entities, va( + "print \"Maximum name changes reached (g_maxNameChanges = %d)\n\"", + g_maxNameChanges.integer ) ); + revertName = qtrue; } - - if( !G_admin_name_check( ent, newname, err, sizeof( err ) ) ) + else if( !forceName && client->pers.namelog->muted ) + { + trap_SendServerCommand( ent - g_entities, + "print \"You cannot change your name while you are muted\n\"" ); + revertName = qtrue; + } + else if( !G_admin_name_check( ent, newname, err, sizeof( err ) ) ) { trap_SendServerCommand( ent - g_entities, va( "print \"%s\n\"", err ) ); revertName = qtrue; @@ -1212,110 +881,96 @@ void ClientUserinfoChanged( int clientNum, qboolean forceName ) if( revertName ) { - Q_strncpyz( client->pers.netname, oldname, + Q_strncpyz( client->pers.netname, *oldname ? oldname : "UnnamedPlayer", sizeof( client->pers.netname ) ); Info_SetValueForKey( userinfo, "name", oldname ); trap_SetUserinfo( clientNum, userinfo ); } else { - Q_strncpyz( client->pers.netname, newname, - sizeof( client->pers.netname ) ); - Info_SetValueForKey( userinfo, "name", newname ); - trap_SetUserinfo( clientNum, userinfo ); - if( client->pers.connected == CON_CONNECTED ) + G_CensorString( client->pers.netname, newname, + sizeof( client->pers.netname ), ent ); + if( !forceName && client->pers.connected == CON_CONNECTED ) + { + client->pers.namelog->nameChangeTime = level.time; + client->pers.namelog->nameChanges++; + } + if( *oldname ) { - client->pers.nameChangeTime = level.time; - client->pers.nameChanges++; + G_LogPrintf( "ClientRename: %i [%s] (%s) \"%s^7\" -> \"%s^7\" \"%c%s%c^7\"\n", + clientNum, client->pers.ip.str, client->pers.guid, + oldname, client->pers.netname, + DECOLOR_OFF, client->pers.netname, DECOLOR_ON ); } } + G_namelog_update_name( client ); } - if( client->sess.sessionTeam == TEAM_SPECTATOR ) + if ( client->pers.teamSelection == TEAM_HUMANS ) { - if( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) - Q_strncpyz( client->pers.netname, "scoreboard", sizeof( client->pers.netname ) ); - } + int i; + qboolean found = qfalse; - if( client->pers.connected >= CON_CONNECTING && showRenameMsg ) - { - if( strcmp( oldname, client->pers.netname ) ) - { - //dont show if players invisible - if( client->sess.invisible != qtrue ) - trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE - " renamed to %s^7\n\"", oldname, client->pers.netname ) ); - if( g_decolourLogfiles.integer) - { - char decoloured[ MAX_STRING_CHARS ] = ""; - if( g_decolourLogfiles.integer == 1 ) - { - Com_sprintf( decoloured, sizeof(decoloured), " (\"%s^7\" -> \"%s^7\")", oldname, client->pers.netname ); - G_DecolorString( decoloured, decoloured ); - G_LogPrintfColoured( "ClientRename: %i [%s] (%s) \"%s^7\" -> \"%s^7\"%s\n", clientNum, - client->pers.ip, client->pers.guid, oldname, client->pers.netname, decoloured ); - } - else - { - G_LogPrintf( "ClientRename: %i [%s] (%s) \"%s^7\" -> \"%s^7\"%s\n", clientNum, - client->pers.ip, client->pers.guid, oldname, client->pers.netname, decoloured ); - } + s = Info_ValueForKey(userinfo, "model"); - } - else + for ( i = 0; i < level.playerModelCount; i++ ) + { + if ( !strcmp(s, level.playerModel[i]) ) { - G_LogPrintf( "ClientRename: %i [%s] (%s) \"%s^7\" -> \"%s^7\"\n", clientNum, - client->pers.ip, client->pers.guid, oldname, client->pers.netname ); + found = qtrue; + break; } - G_admin_namelog_update( client, qfalse ); } - } - // set max health - health = atoi( Info_ValueForKey( userinfo, "handicap" ) ); - client->pers.maxHealth = health; + if ( !found ) + s = NULL; + else if ( !g_cheats.integer + && !forceName + && !G_admin_permission( ent, va("MODEL%s", s) ) ) + s = NULL; - if( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) - client->pers.maxHealth = 100; - - //hack to force a client update if the config string does not change between spawning - if( client->pers.classSelection == PCL_NONE ) - client->pers.maxHealth = 0; - - // set model - if( client->ps.stats[ STAT_PCLASS ] == PCL_HUMAN && BG_InventoryContainsUpgrade( UP_BATTLESUIT, client->ps.stats ) ) + if (s) + { + s2 = Info_ValueForKey(userinfo, "skin"); + s2 = GetSkin(s, s2); + } + } + else { - Com_sprintf( buffer, MAX_QPATH, "%s/%s", BG_FindModelNameForClass( PCL_HUMAN_BSUIT ), - BG_FindSkinNameForClass( PCL_HUMAN_BSUIT ) ); + s = NULL; } - else if( client->pers.classSelection == PCL_NONE ) + + if( client->pers.classSelection == PCL_NONE ) { //This looks hacky and frankly it is. The clientInfo string needs to hold different //model details to that of the spawning class or the info change will not be //registered and an axis appears instead of the player model. There is zero chance //the player can spawn with the battlesuit, hence this choice. - Com_sprintf( buffer, MAX_QPATH, "%s/%s", BG_FindModelNameForClass( PCL_HUMAN_BSUIT ), - BG_FindSkinNameForClass( PCL_HUMAN_BSUIT ) ); + Com_sprintf( buffer, MAX_QPATH, "%s/%s", BG_ClassConfig( PCL_HUMAN_BSUIT )->modelName, + BG_ClassConfig( PCL_HUMAN_BSUIT )->skinName ); } else { - Com_sprintf( buffer, MAX_QPATH, "%s/%s", BG_FindModelNameForClass( client->pers.classSelection ), - BG_FindSkinNameForClass( client->pers.classSelection ) ); - } - Q_strncpyz( model, buffer, sizeof( model ) ); + if ( !(client->pers.classSelection == PCL_HUMAN_BSUIT) && s ) + { + Com_sprintf( buffer, MAX_QPATH, "%s/%s", s, s2 ); + } + else + { + Com_sprintf( buffer, MAX_QPATH, "%s/%s", BG_ClassConfig( client->pers.classSelection )->modelName, + BG_ClassConfig( client->pers.classSelection )->skinName ); + } - //don't bother setting model type if spectating - if( client->pers.classSelection != PCL_NONE ) - { //model segmentation Com_sprintf( filename, sizeof( filename ), "models/players/%s/animation.cfg", - BG_FindModelNameForClass( client->pers.classSelection ) ); + BG_ClassConfig( client->pers.classSelection )->modelName ); if( G_NonSegModel( filename ) ) client->ps.persistant[ PERS_STATE ] |= PS_NONSEGMODEL; else client->ps.persistant[ PERS_STATE ] &= ~PS_NONSEGMODEL; } + Q_strncpyz( model, buffer, sizeof( model ) ); // wallwalk follow s = Info_ValueForKey( userinfo, "cg_wwFollow" ); @@ -1333,13 +988,44 @@ void ClientUserinfoChanged( int clientNum, qboolean forceName ) else client->ps.persistant[ PERS_STATE ] &= ~PS_WALLCLIMBINGTOGGLE; + // always sprint + s = Info_ValueForKey( userinfo, "cg_sprintToggle" ); + + if( atoi( s ) ) + client->ps.persistant[ PERS_STATE ] |= PS_SPRINTTOGGLE; + else + client->ps.persistant[ PERS_STATE ] &= ~PS_SPRINTTOGGLE; + + // fly speed + s = Info_ValueForKey( userinfo, "cg_flySpeed" ); + + if( *s ) + client->pers.flySpeed = atoi( s ); + else + client->pers.flySpeed = BG_Class( PCL_NONE )->speed; + + // disable blueprint errors + s = Info_ValueForKey( userinfo, "cg_disableBlueprintErrors" ); + + if( atoi( s ) ) + client->pers.disableBlueprintErrors = qtrue; + else + client->pers.disableBlueprintErrors = qfalse; + + client->pers.buildableRangeMarkerMask = + atoi( Info_ValueForKey( userinfo, "cg_buildableRangeMarkerMask" ) ); + // teamInfo s = Info_ValueForKey( userinfo, "teamoverlay" ); - if( ! *s || atoi( s ) != 0 ) - client->pers.teamInfo = qtrue; + if( atoi( s ) != 0 ) + { + // teamoverlay was enabled so we need an update + if( client->pers.teamInfo == 0 ) + client->pers.teamInfo = 1; + } else - client->pers.teamInfo = qfalse; + client->pers.teamInfo = 0; s = Info_ValueForKey( userinfo, "cg_unlagged" ); if( !s[0] || atoi( s ) != 0 ) @@ -1347,67 +1033,26 @@ void ClientUserinfoChanged( int clientNum, qboolean forceName ) else client->pers.useUnlagged = qfalse; - // team task (0 = none, 1 = offence, 2 = defence) - teamTask = atoi( Info_ValueForKey( userinfo, "teamtask" ) ); - // team Leader (1 = leader, 0 is normal player) - teamLeader = client->sess.teamLeader; - - // colors - strcpy( c1, Info_ValueForKey( userinfo, "color1" ) ); - strcpy( c2, Info_ValueForKey( userinfo, "color2" ) ); - - team = client->pers.teamSelection; + Q_strncpyz( client->pers.voice, Info_ValueForKey( userinfo, "voice" ), + sizeof( client->pers.voice ) ); // send over a subset of the userinfo keys so other clients can // print scoreboards, display models, and play custom sounds - if ( client->sess.invisible != qtrue ) - { - Com_sprintf( userinfo, sizeof( userinfo ), - "n\\%s\\t\\%i\\model\\%s\\hmodel\\%s\\c1\\%s\\c2\\%s\\" - "hc\\%i\\w\\%i\\l\\%i\\tt\\%d\\" - "tl\\%d\\ig\\%16s", - client->pers.netname, team, model, model, c1, c2, - client->pers.maxHealth, client->sess.wins, client->sess.losses, teamTask, - teamLeader, BG_ClientListString( &client->sess.ignoreList ) ); - - trap_SetConfigstring( CS_PLAYERS + clientNum, userinfo ); - } else { - trap_SetConfigstring( CS_PLAYERS + clientNum, "" ); - } + + Com_sprintf( userinfo, sizeof( userinfo ), + "n\\%s\\t\\%i\\model\\%s\\ig\\%16s\\v\\%s", + client->pers.netname, client->pers.teamSelection, model, + Com_ClientListString( &client->sess.ignoreList ), + client->pers.voice ); + + trap_SetConfigstring( CS_PLAYERS + clientNum, userinfo ); + /*G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, userinfo );*/ -} -/* -=========== -LogAutobahn -=========== -*/ -void G_LogAutobahn(gentity_t *ent, const char *userinfo, int rating, - qboolean onConnect) -{ - char ip_buffer[20]; - const char *ip, *name, *verb; - - verb = (onConnect ? "refused" : "dropped"); - - if (userinfo) { - Q_strncpyz(ip_buffer, Info_ValueForKey(userinfo, "ip"), - sizeof(ip_buffer)); - ip = ip_buffer; - name = Info_ValueForKey(userinfo, "name"); - } else { - ip = ent->client->pers.ip; - name = ent->client->pers.netname; - } - - G_LogPrintf("Autobahn: %s %i %s %+i \"%s^7\"\n", verb, ent - g_entities, - ip, rating, name); - - if (g_adminAutobahnNotify.integer) - G_AdminsPrintf("Autobahn %s '%s^7' with rating %+i, connecting from %s.\n", - verb, name, rating, ip); + return NULL; } + /* =========== ClientConnect @@ -1428,52 +1073,59 @@ to the server machine, but qfalse on map changes and tournement restarts. ============ */ -char *ClientConnect( int clientNum, qboolean firstTime ) +const char *ClientConnect( int clientNum, qboolean firstTime ) { char *value; + char *userInfoError; gclient_t *client; char userinfo[ MAX_INFO_STRING ]; gentity_t *ent; - char guid[ 33 ]; - char ip[ 16 ] = {""}; char reason[ MAX_STRING_CHARS ] = {""}; int i; ent = &g_entities[ clientNum ]; + client = &level.clients[ clientNum ]; + + // ignore if client already connected + if( client->pers.connected != CON_DISCONNECTED ) + return NULL; + + ent->client = client; + memset( client, 0, sizeof( *client ) ); trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); value = Info_ValueForKey( userinfo, "cl_guid" ); - Q_strncpyz( guid, value, sizeof( guid ) ); - - // check for admin ban - if( G_admin_ban_check( userinfo, reason, sizeof( reason ) ) ) - { - return va( "%s", reason ); - } + Q_strncpyz( client->pers.guid, value, sizeof( client->pers.guid ) ); - // IP filtering - // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=500 - // recommanding PB based IP / GUID banning, the builtin system is pretty limited - // check to see if they are on the banned IP list value = Info_ValueForKey( userinfo, "ip" ); - i = 0; - while( *value && i < sizeof( ip ) - 2 ) + // check for local client + if( !strcmp( value, "localhost" ) ) + client->pers.localClient = qtrue; + G_AddressParse( value, &client->pers.ip ); + + client->pers.admin = G_admin_admin( client->pers.guid ); + + client->pers.alternateProtocol = trap_Cvar_VariableIntegerValue( va( "sv_clAltProto%i", clientNum ) ); + + if( client->pers.alternateProtocol == 2 && client->pers.guid[ 0 ] == '\0' ) { - if( *value != '.' && ( *value < '0' || *value > '9' ) ) - break; - ip[ i++ ] = *value; - value++; + size_t len = strlen( client->pers.ip.str ); + if( len == 0 ) + len = 1; + for( i = 0; i < sizeof( client->pers.guid ) - 1; ++i ) + { + int j = client->pers.ip.str[ i % len ] + rand() / ( RAND_MAX / 16 + 1 ); + client->pers.guid[ i ] = "0123456789ABCDEF"[ j % 16 ]; + } + client->pers.guid[ sizeof( client->pers.guid ) - 1 ] = '\0'; + client->pers.guidless = qtrue; } - ip[ i ] = '\0'; - if( G_FilterPacket( value ) ) - return "You are banned from this server."; - if( strlen( ip ) < 7 && strcmp( Info_ValueForKey( userinfo, "ip" ), "localhost" ) ) + // check for admin ban + if( G_admin_ban_check( ent, reason, sizeof( reason ) ) ) { - G_AdminsPrintf( "Connect from client with invalid IP: '%s' NAME: '%s^7'\n", - ip, Info_ValueForKey( userinfo, "name" ) ); - return "Invalid client data"; + return va( "%s", reason ); } // check for a password @@ -1483,66 +1135,12 @@ char *ClientConnect( int clientNum, qboolean firstTime ) strcmp( g_password.string, value ) != 0 ) return "Invalid password"; - schachtmeisterJudgement_t *smj = NULL; - - if (!(G_admin_permission_guid(guid, ADMF_NOAUTOBAHN) - || G_admin_permission_guid(guid, ADMF_IMMUNITY))) - { - extern g_admin_namelog_t *g_admin_namelog[128]; - for (i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[i]; ++i) - { - if (!Q_stricmp(g_admin_namelog[i]->ip, ip) - || !Q_stricmp(g_admin_namelog[i]->guid, guid)) - { - schachtmeisterJudgement_t *j = &g_admin_namelog[i]->smj; - if (j->ratingTime) - { - if (j->rating >= g_schachtmeisterClearThreshold.integer) - break; - else if (j->rating <= g_schachtmeisterAutobahnThreshold.integer) - { - G_LogAutobahn( ent, userinfo, j->rating, qtrue ); - return g_schachtmeisterAutobahnMessage.string; - } - smj = j; - } - break; - } - } - } - - // they can connect - ent->client = level.clients + clientNum; - client = ent->client; - - memset( client, 0, sizeof(*client) ); - // add guid to session so we don't have to keep parsing userinfo everywhere - if( !guid[ 0 ] ) - { - Q_strncpyz( client->pers.guid, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", - sizeof( client->pers.guid ) ); - } - else - { - Q_strncpyz( client->pers.guid, guid, sizeof( client->pers.guid ) ); - } + for( i = 0; i < sizeof( client->pers.guid ) - 1 && + isxdigit( client->pers.guid[ i ] ); i++ ); - Q_strncpyz( client->pers.ip, ip, sizeof( client->pers.ip ) ); - client->pers.adminLevel = G_admin_level( ent ); - - // do autoghost now so that there won't be any name conflicts later on - if ( g_autoGhost.integer && client->pers.guid[ 0 ] != 'X' ) - { - for ( i = 0; i < MAX_CLIENTS; i++ ) - { - if ( i != ent - g_entities && g_entities[i].client && g_entities[i].client->pers.connected != CON_DISCONNECTED && !Q_stricmp( g_entities[i].client->pers.guid, client->pers.guid ) ) - { - trap_SendServerCommand( i, "disconnect \"You may not be connected to this server multiple times\"" ); - trap_DropClient( i, "disconnected" ); - } - } - } + if( i < sizeof( client->pers.guid ) - 1 ) + return "Invalid GUID"; client->pers.connected = CON_CONNECTING; @@ -1552,81 +1150,36 @@ char *ClientConnect( int clientNum, qboolean firstTime ) G_ReadSessionData( client ); - if( firstTime ) - client->pers.firstConnect = qtrue; - else - client->pers.firstConnect = qfalse; - // get and distribute relevent paramters - ClientUserinfoChanged( clientNum, qfalse ); - - G_admin_set_adminname( ent ); - - if( g_decolourLogfiles.integer ) - { - char decoloured[ MAX_STRING_CHARS ] = ""; - if( g_decolourLogfiles.integer == 1 ) - { - Com_sprintf( decoloured, sizeof(decoloured), " (\"%s^7\")", client->pers.netname ); - G_DecolorString( decoloured, decoloured ); - G_LogPrintfColoured( "ClientConnect: %i [%s] (%s) \"%s^7\"%s\n", clientNum, - client->pers.ip, client->pers.guid, client->pers.netname, decoloured ); - } - else - { - G_LogPrintf( "ClientConnect: %i [%s] (%s) \"%s^7\"%s\n", clientNum, - client->pers.ip, client->pers.guid, client->pers.netname, decoloured ); - } - } - else - { - G_LogPrintf( "ClientConnect: %i [%s] (%s) \"%s^7\"\n", clientNum, - client->pers.ip, client->pers.guid, client->pers.netname ); - } - - if( client->pers.adminLevel ) - { - G_LogPrintf( "ClientAuth: %i [%s] \"%s^7\" authenticated to admin level %i using GUID %s (^7%s)\n", clientNum, client->pers.ip, client->pers.netname, client->pers.adminLevel, client->pers.guid, client->pers.adminName ); - } + G_namelog_connect( client ); + userInfoError = ClientUserinfoChanged( clientNum, qfalse ); + if( userInfoError != NULL ) + return userInfoError; + + G_LogPrintf( "ClientConnect: %i [%s] (%s) \"%s^7\" \"%c%s%c^7\"\n", + clientNum, client->pers.ip.str, client->pers.guid, + client->pers.netname, + DECOLOR_OFF, client->pers.netname, DECOLOR_ON ); // don't do the "xxx connected" messages if they were caried over from previous level - if( client->sess.invisible != qtrue ) - { - if( firstTime ) - trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " connected\n\"", client->pers.netname ) ); + if( firstTime ) + trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " connected\n\"", + client->pers.netname ) ); - // count current clients and rank for scoreboard - CalculateRanks( ); - G_admin_namelog_update( client, qfalse ); - } + if( client->pers.admin ) + G_admin_authlog( ent ); + + // count current clients and rank for scoreboard + CalculateRanks( ); + // if this is after !restart keepteams or !restart switchteams, apply said selection - if ( client->sess.restartTeam != PTE_NONE ) { + if ( client->sess.restartTeam != TEAM_NONE ) + { G_ChangeTeam( ent, client->sess.restartTeam ); - client->sess.restartTeam = PTE_NONE; + client->sess.restartTeam = TEAM_NONE; } - if( !( G_admin_permission( ent, ADMF_NOAUTOBAHN ) || - G_admin_permission( ent, ADMF_IMMUNITY ) ) ) - { - extern g_admin_namelog_t *g_admin_namelog[ 128 ]; - for( i = 0; i < MAX_ADMIN_NAMELOGS && g_admin_namelog[ i ]; i++ ) - { - if( !Q_stricmp( ip, g_admin_namelog[ i ]->ip ) || !Q_stricmp( guid, g_admin_namelog[ i ]->guid ) ) - { - schachtmeisterJudgement_t *j = &g_admin_namelog[i]->smj; - if( j->ratingTime ) - { - if( j->rating >= g_schachtmeisterClearThreshold.integer ) - break; - else if( j->rating <= g_schachtmeisterAutobahnThreshold.integer ) - return g_schachtmeisterAutobahnMessage.string; - G_AdminsPrintf( "%s^7 (#%d) has rating %d\n", ent->client->pers.netname, ent - g_entities, j->rating ); - } - break; - } - } - } return NULL; } @@ -1635,9 +1188,9 @@ char *ClientConnect( int clientNum, qboolean firstTime ) =========== ClientBegin -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 +Called when a client has finished connecting, and is ready +to be placed into the level. This will happen on every +level load and level restart, but doesn't happen on respawns. ============ */ void ClientBegin( int clientNum ) @@ -1650,6 +1203,10 @@ void ClientBegin( int clientNum ) client = level.clients + clientNum; + // ignore if client already entered the game + if( client->pers.connected != CON_CONNECTING ) + return; + if( ent->r.linked ) trap_UnlinkEntity( ent ); @@ -1660,8 +1217,6 @@ void ClientBegin( int clientNum ) client->pers.connected = CON_CONNECTED; client->pers.enterTime = level.time; - client->pers.teamState.state = TEAM_BEGIN; - client->pers.classSelection = PCL_NONE; // save eflags around this, because changing teams will // cause this to happen with a valid entity, and we @@ -1674,44 +1229,19 @@ void ClientBegin( int clientNum ) client->ps.eFlags = flags; // locate ent at a spawn point - ClientSpawn( ent, NULL, NULL, NULL ); - // Ignore invisible players for this section: - if ( client->sess.invisible != qtrue ) - { - trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " entered the game\n\"", client->pers.netname ) ); - - // auto denybuild - if( G_admin_permission( ent, ADMF_NO_BUILD ) ) - client->pers.denyBuild = qtrue; - - // auto mute flag - if( G_admin_permission( ent, ADMF_NO_CHAT ) ) - client->pers.muted = qtrue; + trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " entered the game\n\"", client->pers.netname ) ); - // name can change between ClientConnect() and ClientBegin() - G_admin_namelog_update( client, qfalse ); + G_namelog_restore( client ); - if( g_scrimMode.integer == 1 ) - { - ADMP( "^5Scrim mode is enabled. Teams are locked and you can only use spectator chat.\n" ); - } - - // request the clients PTR code - trap_SendServerCommand( ent - g_entities, "ptrcrequest" ); - } G_LogPrintf( "ClientBegin: %i\n", clientNum ); - if( !Q_stricmp( ent->client->pers.guid, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" ) && - g_outdatedClientMessage.string[0] ) - { - trap_SendServerCommand( client->ps.clientNum, va( - "print \"%s\n\"", g_outdatedClientMessage.string ) ); - } - // count current clients and rank for scoreboard CalculateRanks( ); + + // send the client a list of commands that can be used + G_ListCommands( ent ); } /* @@ -1723,7 +1253,7 @@ after the first ClientBegin, and after each respawn Initializes all non-persistant parts of playerState ============ */ -void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles ) +void ClientSpawn( gentity_t *ent, gentity_t *spawn, const vec3_t origin, const vec3_t angles ) { int index; vec3_t spawn_origin, spawn_angles; @@ -1731,8 +1261,8 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles int i; clientPersistant_t saved; clientSession_t savedSess; + qboolean savedNoclip, savedCliprcontents; int persistant[ MAX_PERSISTANT ]; - gentity_t *spawnPoint = NULL; int flags; int savedPing; int teamLocal; @@ -1742,75 +1272,65 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles int maxAmmo, maxClips; weapon_t weapon; - index = ent - g_entities; client = ent->client; teamLocal = client->pers.teamSelection; - //TA: only start client if chosen a class and joined a team - if( client->pers.classSelection == PCL_NONE && teamLocal == PTE_NONE ) + //if client is dead and following teammate, stop following before spawning + if( client->sess.spectatorClient != -1 ) { - client->sess.sessionTeam = TEAM_SPECTATOR; + client->sess.spectatorClient = -1; client->sess.spectatorState = SPECTATOR_FREE; } + + // only start client if chosen a class and joined a team + if( client->pers.classSelection == PCL_NONE && teamLocal == TEAM_NONE ) + client->sess.spectatorState = SPECTATOR_FREE; else if( client->pers.classSelection == PCL_NONE ) - { - client->sess.sessionTeam = TEAM_SPECTATOR; client->sess.spectatorState = SPECTATOR_LOCKED; - } - - //if client is dead and following teammate, stop following before spawning - if(ent->client->sess.spectatorClient!=-1) - { - ent->client->sess.spectatorClient = -1; - ent->client->sess.spectatorState = SPECTATOR_FREE; - } - - if( origin != NULL ) - VectorCopy( origin, spawn_origin ); - if( angles != NULL ) - VectorCopy( angles, spawn_angles ); + // if client is dead and following teammate, stop following before spawning + if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) + G_StopFollowing( ent ); // find a spawn point // do it before setting health back up, so farthest // ranging doesn't count this client - if( client->sess.sessionTeam == TEAM_SPECTATOR ) + if( client->sess.spectatorState != SPECTATOR_NOT ) { - if( teamLocal == PTE_NONE ) - spawnPoint = G_SelectSpectatorSpawnPoint( spawn_origin, spawn_angles ); - else if( teamLocal == PTE_ALIENS ) - spawnPoint = G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles ); - else if( teamLocal == PTE_HUMANS ) - spawnPoint = G_SelectHumanLockSpawnPoint( spawn_origin, spawn_angles ); + if( teamLocal == TEAM_ALIENS ) + spawn = G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles ); + else if( teamLocal == TEAM_HUMANS ) + spawn = G_SelectHumanLockSpawnPoint( spawn_origin, spawn_angles ); + else + spawn = G_SelectSpectatorSpawnPoint( spawn_origin, spawn_angles ); } else { - if( spawn == NULL ) + if( origin == NULL || angles == NULL ) { - G_Error( "ClientSpawn: spawn is NULL\n" ); + G_Error( "ClientSpawn: origin or angles is NULL" ); return; } - spawnPoint = spawn; + VectorCopy( origin, spawn_origin ); + VectorCopy( angles, spawn_angles ); - if( ent != spawn ) + if( spawn != NULL && spawn != ent ) { //start spawn animation on spawnPoint - G_SetBuildableAnim( spawnPoint, BANIM_SPAWN1, qtrue ); + G_SetBuildableAnim( spawn, BANIM_SPAWN1, qtrue ); - if( spawnPoint->biteam == PTE_ALIENS ) - spawnPoint->clientSpawnTime = ALIEN_SPAWN_REPEAT_TIME; - else if( spawnPoint->biteam == PTE_HUMANS ) - spawnPoint->clientSpawnTime = HUMAN_SPAWN_REPEAT_TIME; + if( spawn->buildableTeam == TEAM_ALIENS ) + spawn->clientSpawnTime = ALIEN_SPAWN_REPEAT_TIME; + else if( spawn->buildableTeam == TEAM_HUMANS ) + spawn->clientSpawnTime = HUMAN_SPAWN_REPEAT_TIME; } } - client->pers.teamState.state = TEAM_ACTIVE; // toggle the teleport bit so the client knows to not lerp - flags = ent->client->ps.eFlags & ( EF_TELEPORT_BIT | EF_VOTED | EF_TEAMVOTED ); - flags ^= EF_TELEPORT_BIT; + flags = ( ent->client->ps.eFlags & EF_TELEPORT_BIT ) ^ EF_TELEPORT_BIT; G_UnlaggedClear( ent ); // clear everything but the persistant data @@ -1818,6 +1338,8 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles saved = client->pers; savedSess = client->sess; savedPing = client->ps.ping; + savedNoclip = client->noclip; + savedCliprcontents = client->cliprcontents; for( i = 0; i < MAX_PERSISTANT; i++ ) persistant[ i ] = client->ps.persistant[ i ]; @@ -1828,6 +1350,8 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles client->pers = saved; client->sess = savedSess; client->ps.ping = savedPing; + client->noclip = savedNoclip; + client->cliprcontents = savedCliprcontents; client->lastkilled_client = -1; for( i = 0; i < MAX_PERSISTANT; i++ ) @@ -1837,11 +1361,7 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles // increment the spawncount so the client will detect the respawn client->ps.persistant[ PERS_SPAWN_COUNT ]++; - client->ps.persistant[ PERS_TEAM ] = client->sess.sessionTeam; - - // restore really persistant things - client->ps.persistant[ PERS_SCORE ] = client->pers.score; - client->ps.persistant[ PERS_CREDIT ] = client->pers.credit; + client->ps.persistant[ PERS_SPECSTATE ] = client->sess.spectatorState; client->airOutTime = level.time + 12000; @@ -1853,52 +1373,58 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles 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; + if( client->noclip ) + client->cliprcontents = CONTENTS_BODY; + else + ent->r.contents = CONTENTS_BODY; + if( client->pers.teamSelection == TEAM_NONE ) + ent->clipmask = MASK_DEADSOLID; + else + ent->clipmask = MASK_PLAYERSOLID; ent->die = player_die; ent->waterlevel = 0; ent->watertype = 0; - ent->flags = 0; + ent->flags &= FL_GODMODE | FL_NOTARGET; - //TA: calculate each client's acceleration + // calculate each client's acceleration ent->evaluateAcceleration = qtrue; - client->ps.stats[ STAT_WEAPONS ] = 0; - client->ps.stats[ STAT_WEAPONS2 ] = 0; - client->ps.stats[ STAT_SLOTS ] = 0; + client->ps.stats[ STAT_MISC ] = 0; client->ps.eFlags = flags; client->ps.clientNum = index; - BG_FindBBoxForClass( ent->client->pers.classSelection, ent->r.mins, ent->r.maxs, NULL, NULL, NULL ); + BG_ClassBoundingBox( ent->client->pers.classSelection, ent->r.mins, ent->r.maxs, NULL, NULL, NULL ); - if( client->sess.sessionTeam != TEAM_SPECTATOR ) - client->pers.maxHealth = client->ps.stats[ STAT_MAX_HEALTH ] = - BG_FindHealthForClass( ent->client->pers.classSelection ); + if( client->sess.spectatorState == SPECTATOR_NOT ) + client->ps.stats[ STAT_MAX_HEALTH ] = + BG_Class( ent->client->pers.classSelection )->health; else - client->pers.maxHealth = client->ps.stats[ STAT_MAX_HEALTH ] = 100; + client->ps.stats[ STAT_MAX_HEALTH ] = 100; // clear entity values if( ent->client->pers.classSelection == PCL_HUMAN ) { - BG_AddWeaponToInventory( WP_BLASTER, client->ps.stats ); BG_AddUpgradeToInventory( UP_MEDKIT, client->ps.stats ); weapon = client->pers.humanItemSelection; } - else if( client->sess.sessionTeam != TEAM_SPECTATOR ) - weapon = BG_FindStartWeaponForClass( ent->client->pers.classSelection ); + else if( client->sess.spectatorState == SPECTATOR_NOT ) + weapon = BG_Class( ent->client->pers.classSelection )->startWeapon; else weapon = WP_NONE; - BG_FindAmmoForWeapon( weapon, &maxAmmo, &maxClips ); - BG_AddWeaponToInventory( weapon, client->ps.stats ); + maxAmmo = BG_Weapon( weapon )->maxAmmo; + maxClips = BG_Weapon( weapon )->maxClips; + client->ps.stats[ STAT_WEAPON ] = weapon; client->ps.ammo = maxAmmo; client->ps.clips = maxClips; - ent->client->ps.stats[ STAT_PCLASS ] = ent->client->pers.classSelection; - ent->client->ps.stats[ STAT_PTEAM ] = ent->client->pers.teamSelection; + // We just spawned, not changing weapons + client->ps.persistant[ PERS_NEWWEAPON ] = 0; + + ent->client->ps.stats[ STAT_CLASS ] = ent->client->pers.classSelection; + ent->client->ps.stats[ STAT_TEAM ] = ent->client->pers.teamSelection; ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; ent->client->ps.stats[ STAT_STATE ] = 0; @@ -1911,18 +1437,14 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles if( ent == spawn ) { ent->health *= ent->client->pers.evolveHealthFraction; - client->ps.stats[ STAT_HEALTH ] *= ent->client->pers.evolveHealthFraction; + client->ps.stats[ STAT_HEALTH ] = ent->health; } //clear the credits array for( i = 0; i < MAX_CLIENTS; i++ ) ent->credits[ i ] = 0; - client->ps.stats[ STAT_STAMINA ] = MAX_STAMINA; - - if( mod_jetpackFuel.value >= 10.0f ) { - client->jetpackfuel = mod_jetpackFuel.value; - } + client->ps.stats[ STAT_STAMINA ] = STAMINA_MAX; G_SetOrigin( ent, spawn_origin ); VectorCopy( spawn_origin, client->ps.origin ); @@ -1931,10 +1453,14 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles #define F_VEL 50.0f //give aliens some spawn velocity - if( client->sess.sessionTeam != TEAM_SPECTATOR && - client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) + if( client->sess.spectatorState == SPECTATOR_NOT && + client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) { - if( ent == spawn ) + if( spawn == NULL ) + { + G_AddPredictableEvent( ent, EV_PLAYER_RESPAWN, 0 ); + } + else if( ent == spawn ) { //evolution particle system G_AddPredictableEvent( ent, EV_ALIEN_EVOLVE, DirToByte( up ) ); @@ -1944,13 +1470,13 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles spawn_angles[ YAW ] += 180.0f; AngleNormalize360( spawn_angles[ YAW ] ); - if( spawnPoint->s.origin2[ 2 ] > 0.0f ) + if( spawn->s.origin2[ 2 ] > 0.0f ) { vec3_t forward, dir; AngleVectors( spawn_angles, forward, NULL, NULL ); VectorScale( forward, F_VEL, forward ); - VectorAdd( spawnPoint->s.origin2, forward, dir ); + VectorAdd( spawn->s.origin2, forward, dir ); VectorNormalize( dir ); VectorScale( dir, UP_VEL, client->ps.velocity ); @@ -1959,11 +1485,14 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles G_AddPredictableEvent( ent, EV_PLAYER_RESPAWN, 0 ); } } - else if( client->sess.sessionTeam != TEAM_SPECTATOR && - client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + else if( client->sess.spectatorState == SPECTATOR_NOT && + client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) { - spawn_angles[ YAW ] += 180.0f; - AngleNormalize360( spawn_angles[ YAW ] ); + if( spawn != NULL ) + { + spawn_angles[ YAW ] += 180.0f; + AngleNormalize360( spawn_angles[ YAW ] ); + } } // the respawned flag will be cleared after the attack and jump keys come up @@ -1972,13 +1501,14 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd ); G_SetClientViewAngle( ent, spawn_angles ); - if( !( client->sess.sessionTeam == TEAM_SPECTATOR ) ) + if( client->sess.spectatorState == SPECTATOR_NOT ) { - /*G_KillBox( ent );*/ //blame this if a newly spawned client gets stuck in another trap_LinkEntity( ent ); // force the base weapon up - client->ps.weapon = WP_NONE; + if( client->pers.teamSelection == TEAM_HUMANS ) + G_ForceWeaponChange( ent, weapon ); + client->ps.weaponstate = WEAPON_READY; } @@ -1987,8 +1517,7 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles client->ps.pm_time = 100; client->respawnTime = level.time; - if( g_gradualFreeFunds.integer < 2 ) - client->pers.lastFreekillTime = level.time; + ent->nextRegenTime = level.time; client->inactivityTime = level.time + g_inactivity.integer * 1000; client->latched_buttons = 0; @@ -2002,21 +1531,10 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles else { // fire the targets of the spawn point - if( !spawn ) - G_UseTargets( spawnPoint, ent ); + if( spawn != NULL && spawn != ent ) + G_UseTargets( spawn, ent ); - // select the highest weapon number available, after any - // spawn given items have fired - client->ps.weapon = 1; - - for( i = WP_NUM_WEAPONS - 1; i > 0 ; i-- ) - { - if( BG_InventoryContainsWeapon( i, client->ps.stats ) ) - { - client->ps.weapon = i; - break; - } - } + client->ps.weapon = client->ps.stats[ STAT_WEAPON ]; } // run a client frame to drop exactly to the floor, @@ -2025,15 +1543,16 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles ent->client->pers.cmd.serverTime = level.time; ClientThink( ent-g_entities ); + VectorCopy( ent->client->ps.viewangles, ent->r.currentAngles ); + VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); // positively link the client, even if the command times are weird - if( client->sess.sessionTeam != TEAM_SPECTATOR ) + if( client->sess.spectatorState == SPECTATOR_NOT ) { BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); - VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); trap_LinkEntity( ent ); } - //TA: must do this here so the number of active clients is calculated + // must do this here so the number of active clients is calculated CalculateRanks( ); // run the presend to set anything else @@ -2041,6 +1560,8 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles // clear entity state values BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); + + client->pers.infoChangeTime = level.time; } @@ -2061,55 +1582,40 @@ void ClientDisconnect( int clientNum ) gentity_t *ent; gentity_t *tent; int i; - buildHistory_t *ptr; ent = g_entities + clientNum; - if( !ent->client ) + if( !ent->client || ent->client->pers.connected == CON_DISCONNECTED ) return; - // look through the bhist and readjust it if the referenced ent has left - for( ptr = level.buildHistory; ptr; ptr = ptr->next ) - { - if( ptr->ent == ent ) - { - ptr->ent = NULL; - Q_strncpyz( ptr->name, ent->client->pers.netname, MAX_NETNAME ); - } - } - - if ( ent->client->sess.invisible != qtrue ) - G_admin_namelog_update( ent->client, qtrue ); G_LeaveTeam( ent ); + G_namelog_disconnect( ent->client ); + G_Vote( ent, TEAM_NONE, qfalse ); // stop any following clients for( i = 0; i < level.maxclients; i++ ) { // remove any /ignore settings for this clientNum - BG_ClientListRemove( &level.clients[ i ].sess.ignoreList, clientNum ); + Com_ClientListRemove( &level.clients[ i ].sess.ignoreList, clientNum ); } // send effect if they were completely connected if( ent->client->pers.connected == CON_CONNECTED && - ent->client->sess.sessionTeam != TEAM_SPECTATOR ) + ent->client->sess.spectatorState == SPECTATOR_NOT ) { tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_OUT ); tent->s.clientNum = ent->s.clientNum; } - if( ent->client->pers.connection ) - ent->client->pers.connection->clientNum = -1; - - G_LogPrintf( "ClientDisconnect: %i [%s] (%s) \"%s\"\n", clientNum, - ent->client->pers.ip, ent->client->pers.guid, ent->client->pers.netname ); + G_LogPrintf( "ClientDisconnect: %i [%s] (%s) \"%s^7\"\n", clientNum, + ent->client->pers.ip.str, ent->client->pers.guid, ent->client->pers.netname ); trap_UnlinkEntity( 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->sess.sessionTeam = TEAM_FREE; + ent->client->sess.spectatorState = + ent->client->ps.persistant[ PERS_SPECSTATE ] = SPECTATOR_NOT; trap_SetConfigstring( CS_PLAYERS + clientNum, ""); -- cgit