stvoy-mp-sdk/Code-DM/game/g_client.c
2000-12-11 00:00:00 +00:00

1397 lines
36 KiB
C

// Copyright (C) 1999-2000 Id Software, Inc.
//
#include "g_local.h"
#include "g_groups.h"
extern char* BG_RegisterRace( const char *name );
// g_client.c -- client functions that don't happen every frame
static vec3_t playerMins = {-15, -15, -24};
static vec3_t playerMaxs = {15, 15, 32};
/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) initial
potential spawning position for deathmatch games.
The first time a player enters the game, they will be at an 'initial' spot.
Targets will be fired when someone spawns in on them.
"nobots" will prevent bots from using this spot.
"nohumans" will prevent non-bots from using this spot.
*/
void SP_info_player_deathmatch( gentity_t *ent ) {
int i;
G_SpawnInt( "nobots", "0", &i);
if ( i ) {
ent->flags |= FL_NO_BOTS;
}
G_SpawnInt( "nohumans", "0", &i );
if ( i ) {
ent->flags |= FL_NO_HUMANS;
}
}
/*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32)
equivelant to info_player_deathmatch
*/
void SP_info_player_start(gentity_t *ent) {
ent->classname = "info_player_deathmatch";
SP_info_player_deathmatch( ent );
}
/*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32)
The intermission will be viewed from this point. Target an info_notnull for the view direction.
*/
void SP_info_player_intermission( gentity_t *ent ) {
}
/*
=======================================================================
SelectSpawnPoint
=======================================================================
*/
/*
================
SpotWouldTelefrag
================
*/
qboolean SpotWouldTelefrag( gentity_t *spot ) {
int i, num;
int touch[MAX_GENTITIES];
gentity_t *hit;
vec3_t mins, maxs;
VectorAdd( spot->s.origin, playerMins, mins );
VectorAdd( spot->s.origin, playerMaxs, maxs );
num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
for (i=0 ; i<num ; i++) {
hit = &g_entities[touch[i]];
if ( hit->client && hit->client->ps.stats[STAT_HEALTH] > 0 ) {
return qtrue;
}
}
return qfalse;
}
/*
================
SelectNearestDeathmatchSpawnPoint
Find the spot that we DON'T want to use
================
*/
#define MAX_SPAWN_POINTS 128
gentity_t *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;
}
/*
================
SelectRandomDeathmatchSpawnPoint
go to a random point that doesn't telefrag
================
*/
#define MAX_SPAWN_POINTS 128
gentity_t *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 ];
}
/*
===========
SelectSpawnPoint
Chooses a player start, deathmatch start, etc
============
*/
gentity_t *SelectSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) {
gentity_t *spot;
gentity_t *nearestSpot;
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) {
G_Error( "Couldn't find a spawn point" );
}
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.
============
*/
gentity_t *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 SelectSpawnPoint( vec3_origin, origin, angles );
}
VectorCopy (spot->s.origin, origin);
origin[2] += 9;
VectorCopy (spot->s.angles, angles);
return spot;
}
/*
===========
SelectSpectatorSpawnPoint
============
*/
gentity_t *SelectSpectatorSpawnPoint( vec3_t origin, vec3_t angles ) {
FindIntermissionPoint();
VectorCopy( level.intermission_origin, origin );
VectorCopy( level.intermission_angle, angles );
return NULL;
}
/*
=======================================================================
BODYQUE
=======================================================================
*/
static int bodyFadeSound=0;
/*
===============
InitBodyQue
===============
*/
void InitBodyQue (void) {
int i;
gentity_t *ent;
level.bodyQueIndex = 0;
for (i=0; i<BODY_QUEUE_SIZE ; i++) {
ent = G_Spawn();
ent->classname = "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.
=============
*/
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.
=============
*/
void CopyToBodyQue( gentity_t *ent ) {
gentity_t *body;
int contents;
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 ) {
// 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);
body->s = ent->s;
body->s.eFlags = EF_DEAD; // clear EF_TALK, etc
body->s.powerups = 0; // clear powerups
body->s.loopSound = 0; // clear lava burning
body->s.number = body - g_entities;
body->timestamp = level.time;
body->physicsObject = qtrue;
body->physicsBounce = 0; // don't bounce
if ( body->s.groundEntityNum == ENTITYNUM_NONE ) {
body->s.pos.trType = TR_GRAVITY;
body->s.pos.trTime = level.time;
VectorCopy( ent->client->ps.velocity, body->s.pos.trDelta );
} else {
body->s.pos.trType = TR_STATIONARY;
}
body->s.event = 0;
// change the animation to the last-frame only, so the sequence
// doesn't repeat anew for the body
switch ( body->s.legsAnim & ~ANIM_TOGGLEBIT ) {
case BOTH_DEATH1:
case BOTH_DEAD1:
body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD1;
break;
case BOTH_DEATH2:
case BOTH_DEAD2:
body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD2;
break;
case BOTH_DEATH3:
case BOTH_DEAD3:
default:
body->s.torsoAnim = body->s.legsAnim = BOTH_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 &&
((level.time - ent->client->ps.powerups[PW_DISINTEGRATE]) < 10000 ||
(level.time - ent->client->ps.powerups[PW_EXPLODE]) < 10000))
{
body->s.eFlags |= EF_NODRAW;
}
// don't take more damage if already gibbed
if ( ent->health <= GIB_HEALTH ) {
body->takedamage = qfalse;
} else {
body->takedamage = qtrue;
}
VectorCopy ( body->s.pos.trBase, body->r.currentOrigin );
trap_LinkEntity (body);
}
//======================================================================
/*
==================
SetClientViewAngle
==================
*/
void SetClientViewAngle( 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);
}
/*
================
respawn
================
*/
void respawn( gentity_t *ent ) {
gentity_t *tent;
CopyToBodyQue (ent);
ClientSpawn(ent);
// add a teleportation effect
tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN );
tent->s.clientNum = ent->s.clientNum;
}
/*
================
TeamCount
Returns number of players on a team
================
*/
team_t 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 count;
}
/*
================
PickTeam
================
*/
team_t PickTeam( int ignoreClientNum ) {
int counts[TEAM_NUM_TEAMS];
counts[TEAM_BLUE] = TeamCount( ignoreClientNum, TEAM_BLUE );
counts[TEAM_RED] = 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;
}
return TEAM_BLUE;
}
/*
===========
ForceClientSkin
Forces a client's skin (for teamplay)
===========
*/
void ForceClientSkin(char *model, const char *skin ) {
char *p;
if ((p = strchr(model, '/')) != NULL) {
*p = 0;
}
Q_strcat(model, MAX_QPATH, "/");
Q_strcat(model, MAX_QPATH, skin);
}
/*
===========
ClientCheckName
============
*/
static void ClientCleanName( const char *in, char *out, int outSize ) {
int len, colorlessLen;
char ch;
char *p;
int spaces;
//save room for trailing null byte
outSize--;
len = 0;
colorlessLen = 0;
p = out;
*p = 0;
spaces = 0;
while( 1 ) {
ch = *in++;
if( !ch ) {
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.
===========
*/
qboolean legalSkin(char *race_list, char *race)
{
char current_race_name[125];
char *s = race_list;
char *max_place = race_list + strlen(race_list);
char *marker;
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
Q_strncpyz(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))
{
return qtrue;
}
}
return qfalse;
}
/*
===========
randomSkin
given a race name, go find all the skins that use it, and randomly select one
===========
*/
void randomSkin(char* race, char* model, int current_team, int clientNum)
{
char skinsForRace[MAX_SKINS_FOR_RACE][128];
int howManySkins = 0;
int i,x;
int temp;
int skin_count_check;
char skinNamesAlreadyUsed[16][128];
int current_skin_count = 0;
gentity_t *ent = NULL;
char userinfo[MAX_INFO_STRING];
char temp_model[MAX_QPATH];
memset(skinsForRace, 0, sizeof(skinsForRace));
memset(skinNamesAlreadyUsed, 0, sizeof(skinNamesAlreadyUsed));
// 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)
{
// 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 (i=0; i<g_maxclients.integer; i++)
{
// did we find enough skins to grab a random one from yet?
if (current_skin_count == skin_count_check)
{
break;
}
ent = g_entities + i;
if (!ent->inuse || 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 && ent->client->sess.sessionTeam == current_team)
{
// so what's this clients model then?
trap_GetUserinfo( i, userinfo, sizeof( userinfo ) );
Q_strncpyz( temp_model, Info_ValueForKey (userinfo, "model"), sizeof( temp_model ) );
// check the name
for (x = 0; x< current_skin_count; x++)
{
// are we the same?
if (!Q_stricmp(skinNamesAlreadyUsed[x], temp_model))
{
// 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, "");
return;
}
}
// search through each and every skin we can find
for (i=0; i<group_count && howManySkins < MAX_SKINS_FOR_RACE; i++)
{
// if this models race list contains the race we want, then add it to the list
if (legalSkin(group_list[i].text, race))
{
Q_strncpyz( skinsForRace[howManySkins++], group_list[i].name , 128 );
}
}
// set model to a random one
if (howManySkins)
{
temp = rand() % howManySkins;
Q_strncpyz( model, skinsForRace[temp], MAX_QPATH );
}
else
{
model[0] = 0;
}
}
/*
===========
getNewSkin
Go away and actually get a random new skin based on a group name
============
*/
qboolean getNewSkin(char *group, char *model, char *color, gclient_t *client, int clientNum)
{
char *temp_string;
// 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;
}
/*
===========
ClientUserInfoChanged
Called from ClientConnect 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 ClientUserinfoChanged( int clientNum ) {
gentity_t *ent;
char *s;
char model[MAX_QPATH];
char oldname[MAX_STRING_CHARS];
gclient_t *client;
char *c1;
char userinfo[MAX_INFO_STRING];
qboolean reset;
ent = g_entities + clientNum;
client = ent->client;
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" ) ) {
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;
}
// set name
Q_strncpyz ( oldname, client->pers.netname, sizeof( oldname ) );
s = Info_ValueForKey (userinfo, "name");
ClientCleanName( s, client->pers.netname, sizeof(client->pers.netname) );
if ( client->sess.sessionTeam == TEAM_SPECTATOR ) {
if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) {
Q_strncpyz( client->pers.netname, "scoreboard", sizeof(client->pers.netname) );
}
}
if ( client->pers.connected == CON_CONNECTED ) {
if ( strcmp( oldname, client->pers.netname ) ) {
trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " renamed to %s\n\"", oldname,
client->pers.netname) );
}
}
// set max health
client->pers.maxHealth = atoi( Info_ValueForKey( userinfo, "handicap" ) );
if ( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) {
client->pers.maxHealth = 100;
}
client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth;
// set model
Q_strncpyz( model, Info_ValueForKey (userinfo, "model"), sizeof( model ) );
// team
switch( client->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))
{
ForceClientSkin(model, "red");
break;
}
// at this point, we are playing CTF and there IS a race specified for this game
else
{
// go away and get what ever races this skin is attached to.
reset = getNewSkin(g_team_group_red.string, model, "red", client, clientNum);
// did we get a model name back?
if (!model[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)
{
trap_SendServerCommand( -1, va("print \"In-appropriate skin selected for %s on team %s\nSkin selection overridden from skin %s to skin %s\n\"",
client->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))
{
ForceClientSkin(model, "blue");
break;
}
// at this point, we are playing CTF and there IS a race specified for this game
else
{
// 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])
{
// 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)
{
trap_SendServerCommand( -1, va("print \"In-appropriate skin selected for %s on team %s\nSkin selection overridden from skin %s to skin %s\n\"",
client->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;
}
}
if ( g_gametype.integer >= GT_TEAM && client->sess.sessionTeam == TEAM_SPECTATOR ) {
// don't ever use a default skin in teamplay, it would just waste memory
ForceClientSkin(model, "red");
}
// colors
c1 = Info_ValueForKey( userinfo, "color" );
// teamInfo
s = Info_ValueForKey( userinfo, "teamoverlay" );
if ( ! *s || atoi( s ) != 0 ) {
client->pers.teamInfo = qtrue;
} else {
client->pers.teamInfo = qfalse;
}
// send over a subset of the userinfo keys so other clients can
// print scoreboards, display models, and play custom sounds
if ( ent->r.svFlags & SVF_BOT ) {
s = va("n\\%s\\t\\%i\\model\\%s\\c1\\%s\\hc\\%i\\w\\%i\\l\\%i\\skill\\%s",
client->pers.netname, client->sess.sessionTeam, model, c1,
client->pers.maxHealth, client->sess.wins, client->sess.losses,
Info_ValueForKey( userinfo, "skill" ) );
} else {
s = va("n\\%s\\t\\%i\\model\\%s\\c1\\%s\\hc\\%i\\w\\%i\\l\\%i",
client->pers.netname, client->sess.sessionTeam, model, c1,
client->pers.maxHealth, client->sess.wins, client->sess.losses );
}
trap_SetConfigstring( CS_PLAYERS+clientNum, s );
G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, s );
}
/*
===========
ClientConnect
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 ClientBegin.
firstTime will be qtrue the very first time a client connects
to the server machine, but qfalse on map changes and tournement
restarts.
============
*/
char *ClientConnect( int clientNum, qboolean firstTime, qboolean isBot ) {
char *value;
gclient_t *client;
char userinfo[MAX_INFO_STRING];
gentity_t *ent;
ent = &g_entities[ clientNum ];
trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) );
// check to see if they are on the banned IP list
value = Info_ValueForKey (userinfo, "ip");
if ( G_FilterPacket( value ) ) {
return "Banned.";
}
// check for a password
value = Info_ValueForKey (userinfo, "password");
if ( g_password.string[0] && Q_stricmp( g_password.string, "none" ) &&
strcmp( g_password.string, value) != 0) {
return "Invalid password";
}
// 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\n", clientNum );
ClientUserinfoChanged( clientNum );
// don't do the "xxx connected" messages if they were caried over from previous level
if ( firstTime ) {
trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " connected\n\"", client->pers.netname) );
}
if ( g_gametype.integer >= GT_TEAM &&
client->sess.sessionTeam != TEAM_SPECTATOR ) {
BroadcastTeamChange( client, -1 );
}
// count current clients and rank for scoreboard
CalculateRanks();
return NULL;
}
/*
===========
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
============
*/
void ClientBegin( int clientNum ) {
gentity_t *ent;
gclient_t *client;
gentity_t *tent;
int flags;
ent = g_entities + clientNum;
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;
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
flags = client->ps.eFlags;
memset( &client->ps, 0, sizeof( client->ps ) );
client->ps.eFlags = flags;
// locate ent at a spawn point
ClientSpawn( ent );
if ( client->sess.sessionTeam != TEAM_SPECTATOR && g_holoIntro.integer==0)
{ // Don't use transporter FX for spectators or those watching the holodoors.
// send event
tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN );
tent->s.clientNum = ent->s.clientNum;
if ( g_gametype.integer != GT_TOURNAMENT ) {
trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " entered the game\n\"", client->pers.netname) );
}
}
G_LogPrintf( "ClientBegin: %i\n", clientNum );
// count current clients and rank for scoreboard
CalculateRanks();
// Use intro holodeck door if desired and we did not come from a restart
if (g_holoIntro.integer && !(ent->r.svFlags & SVF_BOT) && !(level.restarted) && !(g_restarted.integer))
{
// kef -- also, don't do this if we're in intermission
if (!level.intermissiontime)
{
client->ps.introTime = level.time + TIME_INTRO;
client->ps.pm_type = PM_FREEZE;
if (g_ghostRespawn.integer)
{
ent->client->ps.powerups[PW_GHOST] = level.time + (g_ghostRespawn.integer * 1000) + TIME_INTRO;
}
}
}
if (level.restarted || g_restarted.integer)
{
trap_Cvar_Set( "g_restarted", "0" );
level.restarted = qfalse;
}
// kef -- should reset all of our awards-related stuff
G_ClearClientLog(clientNum);
}
/*
===========
ClientSpawn
Called every time a client is placed fresh in the world:
after the first ClientBegin, and after each respawn
Initializes all non-persistant parts of playerState
============
*/
void ClientSpawn(gentity_t *ent) {
int index;
vec3_t spawn_origin, spawn_angles;
gclient_t *client;
int i;
clientPersistant_t saved;
clientSession_t savedSess;
int persistant[MAX_PERSISTANT];
gentity_t *spawnPoint;
int flags;
int savedPing;
index = ent - g_entities;
client = ent->client;
// 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 ) {
spawnPoint = SelectSpectatorSpawnPoint (
spawn_origin, spawn_angles);
} else if (g_gametype.integer == GT_CTF) {
spawnPoint = SelectCTFSpawnPoint (
client->sess.sessionTeam,
client->pers.teamState.state,
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 = 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 ) && ( ent->r.svFlags & SVF_BOT ) ) {
continue; // try again
}
// just to be symetric, we have a nohumans option...
if ( ( spawnPoint->flags & FL_NO_HUMANS ) && !( ent->r.svFlags & SVF_BOT ) ) {
continue; // try again
}
break;
} while ( 1 );
}
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;
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];
}
memset (client, 0, sizeof(*client));
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
client->ps.persistant[PERS_SPAWN_COUNT]++;
client->ps.persistant[PERS_TEAM] = client->sess.sessionTeam;
client->airOutTime = level.time + 12000;
// clear entity values
client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth;
client->ps.eFlags = flags;
client->streakCount = 0;
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 = player_die;
ent->waterlevel = 0;
ent->watertype = 0;
ent->flags = 0;
VectorCopy (playerMins, ent->r.mins);
VectorCopy (playerMaxs, ent->r.maxs);
client->ps.clientNum = index;
client->ps.stats[STAT_WEAPONS] = ( 1 << WP_PHASER );
client->ps.ammo[WP_PHASER] = PHASER_AMMO_MAX;
// health will count down towards max_health
ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] * 1.25;
// Start with a small amount of armor as well.
client->ps.stats[STAT_ARMOR] = client->ps.stats[STAT_MAX_HEALTH] * 0.25;
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
client->ps.pm_flags |= PMF_RESPAWNED;
trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd );
SetClientViewAngle( ent, spawn_angles );
if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) {
} else {
G_KillBox( ent );
trap_LinkEntity (ent);
// force the base weapon up
client->ps.weapon = WP_PHASER;
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 ghosting is engaged, allow the player to be invulnerable briefly.
if (g_ghostRespawn.integer)
{
ent->client->ps.powerups[PW_GHOST] = level.time + (g_ghostRespawn.integer * 1000);
}
client->respawnTime = level.time;
client->inactivityTime = level.time + g_inactivity.integer * 1000;
client->latched_buttons = 0;
// set default animations
client->ps.torsoAnim = TORSO_STAND;
client->ps.legsAnim = LEGS_IDLE;
if ( level.intermissiontime ) {
MoveClientToIntermission( ent );
} else {
// fire the targets of the spawn point
G_UseTargets( spawnPoint, 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 ( client->ps.stats[STAT_WEAPONS] & ( 1 << i ) ) {
client->ps.weapon = i;
break;
}
}
}
// 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 );
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 );
}
/*
===========
ClientDisconnect
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 ClientDisconnect( int clientNum ) {
gentity_t *ent;
gentity_t *tent;
int i;
ent = g_entities + clientNum;
if ( !ent->client ) {
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] );
}
}
// send effect if they were completely connected
if ( ent->client->pers.connected == CON_CONNECTED
&& ent->client->sess.sessionTeam != TEAM_SPECTATOR ) {
tent = G_TempEntity( ent->client->ps.origin, 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
TossClientItems ( ent );
}
G_LogPrintf( "ClientDisconnect: %i\n", clientNum );
// if we are playing in tourney mode and losing, give a win to the other player
if ( g_gametype.integer == GT_TOURNAMENT && !level.intermissiontime
&& !level.warmupTime && level.sortedClients[1] == clientNum ) {
level.clients[ level.sortedClients[0] ].sess.wins++;
ClientUserinfoChanged( level.sortedClients[0] );
}
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;
trap_SetConfigstring( CS_PLAYERS + clientNum, "");
CalculateRanks();
if ( ent->r.svFlags & SVF_BOT ) {
BotAIShutdownClient( clientNum );
}
// kef -- if this guy contributed to any of our kills/deaths/weapons logs, clean 'em out
G_ClearClientLog(clientNum);
}