#include "g_local.h" #include "g_functions.h" #include "anims.h" #include "boltOns.h" extern void Q3_DebugPrint( int level, const char *format, ... ); // g_client.c -- client functions that don't happen every frame float DEFAULT_MINS_0 = -12; float DEFAULT_MINS_1 = -12; float DEFAULT_MAXS_0 = 12; float DEFAULT_MAXS_1 = 12; float DEFAULT_PLAYER_RADIUS = sqrt((DEFAULT_MAXS_0*DEFAULT_MAXS_0) + (DEFAULT_MAXS_1*DEFAULT_MAXS_1)); vec3_t playerMins = {DEFAULT_MINS_0, DEFAULT_MINS_1, DEFAULT_MINS_2}; vec3_t playerMaxs = {DEFAULT_MAXS_0, DEFAULT_MAXS_1, DEFAULT_MAXS_2}; void SP_misc_teleporter_dest (gentity_t *ent); /*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) - - NODRAW potential spawning position for deathmatch games. Targets will be fired when someone spawns in on them. */ void SP_info_player_deathmatch(gentity_t *ent) { SP_misc_teleporter_dest (ent); } /*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32) KEEP_PREV DROPTOFLOOR x x x SILENTSPAWN NOWEAPON x KEEP_PREV - keep previous health/ammo/etc DROPTOFLOOR - Player will start on the first solid structure under it SILENTSPAWN - No teleporter sound Targets will be fired when someone spawns in on them. equivalant to info_player_deathmatch */ void SP_info_player_start(gentity_t *ent) { ent->classname = "info_player_deathmatch"; ent->spawnflags |= 1; // James suggests force-ORing the KEEP_PREV flag in for now SP_info_player_deathmatch( ent ); } /* ======================================================================= SelectSpawnPoint ======================================================================= */ /* ================ SpotWouldTelefrag ================ */ qboolean SpotWouldTelefrag( gentity_t *spot, team_t checkteam ) { int i, num; gentity_t *touch[MAX_GENTITIES], *hit; vec3_t mins, maxs; // If we have a mins, use that instead of the hardcoded bounding box if ( spot->mins && VectorLength( spot->mins ) ) VectorAdd( spot->s.origin, spot->mins, mins ); else VectorAdd( spot->s.origin, playerMins, mins ); // If we have a maxs, use that instead of the hardcoded bounding box if ( spot->maxs && VectorLength( spot->maxs ) ) VectorAdd( spot->s.origin, spot->maxs, maxs ); else VectorAdd( spot->s.origin, playerMaxs, maxs ); num = gi.EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); for (i=0 ; iclient && hit->client->ps.stats[STAT_HEALTH] > 0 ) { if ( hit->contents & CONTENTS_BODY ) { if( checkteam == TEAM_FREE || hit->client->playerTeam == checkteam ) {//checking against teammates only...? return qtrue; } } } } return qfalse; } qboolean SpotWouldTelefrag2( gentity_t *mover, vec3_t dest ) { int i, num; gentity_t *touch[MAX_GENTITIES], *hit; vec3_t mins, maxs; VectorAdd( dest, mover->mins, mins ); VectorAdd( dest, mover->maxs, maxs ); num = gi.EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); for (i=0 ; icontents & mover->contents ) { 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, team_t team ) { 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) { /*if ( team == TEAM_RED && ( spot->spawnflags & 2 ) ) { continue; } if ( team == TEAM_BLUE && ( spot->spawnflags & 1 ) ) { continue; }*/ if ( spot->targetname != NULL ) { //this search routine should never find a spot that is targetted continue; } 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( team_t team ) { 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 ( team == TEAM_RED && ( spot->spawnflags & 2 ) ) { continue; } if ( team == TEAM_BLUE && ( spot->spawnflags & 1 ) ) { continue; }*/ if ( spot->targetname != NULL ) { //this search routine should never find a spot that is targetted continue; } if ( SpotWouldTelefrag( spot, TEAM_FREE ) ) { continue; } spots[ count ] = spot; count++; } if ( !count ) { // no spots that won't telefrag spot = G_Find( NULL, FOFS(classname), "info_player_deathmatch"); if ( spot->targetname != NULL ) { //this search routine should never find a spot that is targetted return NULL; } else { return spot; } } selection = rand() % count; return spots[ selection ]; } /* =========== SelectSpawnPoint Chooses a player start, deathmatch start, etc ============ */ gentity_t *SelectSpawnPoint ( vec3_t avoidPoint, team_t team, vec3_t origin, vec3_t angles ) { gentity_t *spot; gentity_t *nearestSpot; if ( level.spawntarget != NULL && level.spawntarget[0] ) {//we have a spawnpoint specified, try to find it if ( (nearestSpot = spot = G_Find( NULL, FOFS(targetname), level.spawntarget )) == NULL ) {//you HAVE to be able to find the desired spot G_Error( "Couldn't find spawntarget %s\n", level.spawntarget ); return NULL; } } else {//not looking for a special startspot nearestSpot = SelectNearestDeathmatchSpawnPoint( avoidPoint, team ); spot = SelectRandomDeathmatchSpawnPoint ( team ); if ( spot == nearestSpot ) { // roll again if it would be real close to point of death spot = SelectRandomDeathmatchSpawnPoint ( team ); } } // find a single player start spot if (!spot) { G_Error( "Couldn't find a spawn point\n" ); } VectorCopy( spot->s.origin, origin ); if ( spot->spawnflags & 2 ) { trace_t tr; origin[2] -= 4096; gi.trace(&tr, spot->s.origin, playerMins, playerMaxs, origin, -1, MASK_PLAYERSOLID ); if ( tr.fraction < 1.0 && !tr.allsolid && !tr.startsolid ) {//found a floor VectorCopy(tr.endpos, origin ); } else {//In solid or too far VectorCopy( spot->s.origin, origin ); } } origin[2] += 9; VectorCopy (spot->s.angles, angles); return spot; } //====================================================================== /* ================== SetClientViewAngle ================== */ void SetClientViewAngle( gentity_t *ent, vec3_t angle ) { int i; // set the delta angle for (i=0 ; i<3 ; i++) { ent->client->ps.delta_angles[i] = ANGLE2SHORT(angle[i]) - 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 ) { if (Q_stricmpn(level.mapname,"_holo",5)) { gi.SendConsoleCommand("load *respawn\n"); // special case } else {//we're on the holodeck int flags; gentity_t *tent; // toggle the teleport bit so the client knows to not lerp flags = ent->client->ps.eFlags; ClientSpawn(ent, eNO/*qfalse*/); // SavedGameJustLoaded_e ent->client->ps.eFlags = flags ^ EF_TELEPORT_BIT; // add a teleportation effect tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN ); tent->s.clientNum = ent->s.clientNum; } } /* ================ PickTeam ================ */ team_t PickTeam( int ignoreClientNum ) { int i; int counts[TEAM_NUM_TEAMS]; memset( counts, 0, sizeof( counts ) ); for ( i = 0 ; i < level.maxclients ; i++ ) { if ( i == ignoreClientNum ) { continue; } if ( level.clients[i].pers.connected == CON_DISCONNECTED ) { continue; } } return TEAM_FREE; } /* =========== ForceClientSkin Forces a client's skin (for teamplay) =========== */ void ForceClientSkin( gclient_t *client, 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); } /* =========== 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 gi.SetUserinfo if desired. ============ */ void ClientUserinfoChanged( int clientNum ) { gentity_t *ent; char *s; char headModel[MAX_QPATH]; char torsoModel[MAX_QPATH]; char legsModel[MAX_QPATH]; char sound[MAX_QPATH]; char oldname[MAX_STRING_CHARS]; gclient_t *client; char *sex; char userinfo[MAX_INFO_STRING]; ent = g_entities + clientNum; client = ent->client; gi.GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); // check for malformed or illegal info strings if ( !Info_Validate(userinfo) ) { strcpy (userinfo, "\\name\\badinfo"); } // check for lcoal client s = Info_ValueForKey( userinfo, "ip" ); if ( !strcmp( s, "localhost" ) ) { client->pers.localClient = qtrue; } // set name Q_strncpyz ( oldname, client->pers.netname, sizeof( oldname ) ); s = Info_ValueForKey (userinfo, "name"); Q_strncpyz( client->pers.netname, s, sizeof(client->pers.netname) ); if ( client->pers.connected == CON_CONNECTED ) { if ( strcmp( oldname, client->pers.netname ) ) { gi.SendServerCommand( -1, "print \"%s 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 > 200 ) { client->pers.maxHealth = 200; } extern cvar_t *g_spskill; if (g_spskill->integer && client->pers.maxHealth > 100) { client->pers.maxHealth = 100; //no cheating here, only allow "easy" in normal skill } client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; // sounds Q_strncpyz( sound, Info_ValueForKey (userinfo, "snd"), sizeof( sound ) ); // set model Q_strncpyz( headModel, Info_ValueForKey (userinfo, "headModel"), sizeof( headModel ) ); Q_strncpyz( torsoModel, Info_ValueForKey (userinfo, "torsoModel"), sizeof( torsoModel ) ); Q_strncpyz( legsModel, Info_ValueForKey (userinfo, "legsModel"), sizeof( legsModel ) ); // sex sex = Info_ValueForKey( userinfo, "sex" ); if ( !sex[0] ) { sex = "m"; } // send over a subset of the userinfo keys so other clients can // print scoreboards, display models, and play custom sounds s = va("n\\%s\\t\\%i\\headModel\\%s\\torsoModel\\%s\\legsModel\\%s\\sex\\%s\\hc\\%i", client->pers.netname, client->sess.sessionTeam, headModel, torsoModel, legsModel, sex, client->pers.maxHealth ); gi.SetConfigstring( CS_PLAYERS+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, SavedGameJustLoaded_e eSavedGameJustLoaded ) { gclient_t *client; char userinfo[MAX_INFO_STRING]; gentity_t *ent; clientSession_t savedSess; clientTourSession_t savedTourSess; ent = &g_entities[ clientNum ]; gi.GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); // they can connect ent->client = level.clients + clientNum; client = ent->client; // if (!qbFromSavedGame) if (eSavedGameJustLoaded != eFULL) { savedSess = client->sess; // savedTourSess = client->tourSess; memset( client, 0, sizeof(*client) ); client->sess = savedSess; client->tourSess = savedTourSess; } client->pers.connected = CON_CONNECTING; if (eSavedGameJustLoaded == eFULL)//qbFromSavedGame) { // G_WriteClientSessionData( client ); // forget it, this is DM stuff anyway // get and distribute relevent paramters ClientUserinfoChanged( clientNum ); } else { // read or initialize the session data if ( firstTime ) { G_InitSessionData( client, userinfo ); } G_ReadSessionData( client ); // get and distribute relevent paramters ClientUserinfoChanged( clientNum ); // don't do the "xxx connected" messages if they were caried over from previous level if ( firstTime ) { gi.SendServerCommand( -1, "print \"%s connected\n\"", client->pers.netname); } } 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, usercmd_t *cmd, SavedGameJustLoaded_e eSavedGameJustLoaded) // qboolean qbFromSavedGame { gentity_t *ent; gclient_t *client; ent = g_entities + clientNum; client = level.clients + clientNum; if (eSavedGameJustLoaded == eFULL)//qbFromSavedGame) { client->pers.connected = CON_CONNECTED; ent->client = client; ClientSpawn( ent, eSavedGameJustLoaded ); } else { if ( ent->linked ) { gi.unlinkentity( ent ); } G_InitGentity( ent ); ent->e_TouchFunc = touchF_NULL; ent->e_PainFunc = painF_NULL; ent->client = client; client->pers.connected = CON_CONNECTED; client->pers.enterTime = level.time; client->pers.teamState.state = TEAM_BEGIN; VectorCopy( cmd->angles, client->pers.cmd_angles ); memset( &client->ps, 0, sizeof( client->ps ) ); // locate ent at a spawn point if ( ClientSpawn( ent, eSavedGameJustLoaded) ) // SavedGameJustLoaded_e { gentity_t *tent; // send teleport event tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN ); tent->owner = ent; tent->s.clientNum = ent->s.clientNum; } } } /* ============ Player_CacheFromPrevLevel Description : just need to grab the weapon items we're going to have when we spawn so they'll be cached Return type : void Argument : void ============ */ void Player_CacheFromPrevLevel(void) { char s[MAX_STRING_CHARS]; gi.Cvar_VariableStringBuffer( sCVARNAME_PLAYERSAVE, s, sizeof(s) ); if (strlen(s)) // actually this would be safe anyway because of the way sscanf() works, but this is clearer { int iDummy, bits; sscanf( s, "%i %i %i", &iDummy, //client->ps.stats[STAT_HEALTH], &iDummy, //client->ps.stats[STAT_ARMOR], &bits //client->ps.stats[STAT_WEAPONS] ); for ( int i = 1 ; i < 16 ; i++ ) { if ( bits & ( 1 << i ) ) { RegisterItem( FindItemForWeapon( (weapon_t)i ) ); } } } } /* ============ Player_RestoreFromPrevLevel Description : retrieve maptransition data recorded by server when exiting previous level (to carry over weapons/ammo/health/etc) Return type : void Argument : gentity_t *ent ============ */ void Player_RestoreFromPrevLevel(gentity_t *ent) { gclient_t *client = ent->client; int i; assert(client); if (client) // though I can't see it not being true... { char s[MAX_STRING_CHARS]; gi.Cvar_VariableStringBuffer( sCVARNAME_PLAYERSAVE, s, sizeof(s) ); if (strlen(s)) // actually this would be safe anyway because of the way sscanf() works, but this is clearer { sscanf( s, "%i %i %i %i %i %f %f %f", &client->ps.stats[STAT_HEALTH], &client->ps.stats[STAT_ARMOR], &client->ps.stats[STAT_WEAPONS], &client->ps.weapon, &client->ps.weaponstate, &client->ps.viewangles[0], &client->ps.viewangles[1], &client->ps.viewangles[2] ); ent->health = client->ps.stats[STAT_HEALTH]; // slight issue with ths for the moment in that although it'll correctly restore angles it doesn't take into account // the overall map orientation, so (eg) exiting east to enter south will be out by 90 degrees, best keep spawn angles for now // // VectorClear (ent->client->pers.cmd_angles); // // SetClientViewAngle( ent, ent->client->ps.viewangles); for ( i = 0; i < AMMO_MAX; i++ ) { gi.Cvar_VariableStringBuffer( va("playerammo%d",i), s, sizeof(s) ); sscanf( s,"%i",&client->ps.ammo[i]); } // Get the borg adapt hits per weapon for ( i = 0; i < MAX_WEAPONS; i++ ) { gi.Cvar_VariableStringBuffer( va("borgadapt%d",i), s, sizeof(s) ); sscanf( s,"%i",&client->ps.borgAdaptHits[i]); } } } } /* =========== 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 ============ */ qboolean ClientSpawn(gentity_t *ent, SavedGameJustLoaded_e eSavedGameJustLoaded ) { int index; vec3_t spawn_origin, spawn_angles; gclient_t *client; int i; clientPersistant_t saved; clientSession_t savedSess; clientTourSession_t savedTourSess; clientInfo_t savedCi; int persistant[MAX_PERSISTANT]; usercmd_t ucmd; gentity_t *spawnPoint; qboolean beamInEffect = qfalse; extern qboolean g_qbLoadTransition; index = ent - g_entities; client = ent->client; if ( eSavedGameJustLoaded == eFULL && g_qbLoadTransition == qfalse )//qbFromSavedGame) { ent->client->pers.teamState.state = TEAM_ACTIVE; // 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; for (i=0; i<3; i++) { ent->client->pers.cmd_angles[i] = 0.0f; } SetClientViewAngle( ent, ent->client->ps.viewangles);//spawn_angles ); gi.linkentity (ent); // run the presend to set anything else ClientEndFrame( ent ); // clear entity state values PlayerStateToEntityState( &client->ps, &ent->s ); } else { // find a spawn point // do it before setting health back up, so farthest // ranging doesn't count this client // don't spawn near existing origin if possible spawnPoint = SelectSpawnPoint ( ent->client->ps.origin, (team_t) ent->client->ps.persistant[PERS_TEAM], spawn_origin, spawn_angles); if ( !(spawnPoint->spawnflags & 32) ) {//Do teleport effect beamInEffect = qtrue; } ent->client->pers.teamState.state = TEAM_ACTIVE; // clear everything but the persistant data saved = client->pers; savedSess = client->sess; savedTourSess = client->tourSess; for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) { persistant[i] = client->ps.persistant[i]; } //Preserve clientInfo memcpy (&savedCi, &client->clientInfo, sizeof(clientInfo_t)); memset (client, 0, sizeof(*client)); memcpy (&client->clientInfo, &savedCi, sizeof(clientInfo_t)); client->pers = saved; client->sess = savedSess; client->tourSess = savedTourSess; 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; ent->s.groundEntityNum = -1; ent->client = &level.clients[index]; ent->takedamage = qtrue; ent->inuse = qtrue; ent->classname = "player"; client->squadname = "Munro"; ent->targetname = "Munro"; ent->script_targetname = "Munro"; ent->NPC_type = "munro"; client->playerTeam = TEAM_STARFLEET; ent->contents = CONTENTS_BODY; ent->clipmask = MASK_PLAYERSOLID; ent->e_DieFunc = dieF_player_die; ent->waterlevel = 0; ent->watertype = 0; client->ps.friction = 6; client->ps.gravity = g_gravity->value; ent->flags &= ~FL_NO_KNOCKBACK; client->renderInfo.scaleXYZ[0] = client->renderInfo.scaleXYZ[1] = client->renderInfo.scaleXYZ[2] = 100; client->renderInfo.lookTarget = ENTITYNUM_NONE; VectorCopy (playerMins, ent->mins); VectorCopy (playerMaxs, ent->maxs); client->crouchheight = CROUCH_MAXS_2; client->standheight = DEFAULT_MAXS_2; client->ps.clientNum = index; //give default weapons client->ps.stats[STAT_WEAPONS] = ( 1 << WP_NONE ); client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_PHASER ); //these are precached in g_items, ClearRegisteredItems() client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_COMPRESSION_RIFLE ); client->ps.ammo[AMMO_STARFLEET] = ammoData[AMMO_STARFLEET].max; client->ps.ammo[AMMO_ALIEN] = ammoData[AMMO_ALIEN].max; client->ps.ammo[AMMO_PHASER] = ammoData[AMMO_PHASER].max; ent->health = client->ps.stats[STAT_ARMOR] = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH]; VectorCopy( spawn_origin, client->ps.origin ); VectorCopy( spawn_origin, ent->currentOrigin ); // the respawned flag will be cleared after the attack and jump keys come up client->ps.pm_flags |= PMF_RESPAWNED; SetClientViewAngle( ent, spawn_angles ); { G_KillBox( ent ); gi.linkentity (ent); // force the base weapon up client->ps.weapon = WP_COMPRESSION_RIFLE; 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; client->respawnTime = level.time; client->inactivityTime = level.time + g_inactivity->integer * 1000; client->latched_buttons = 0; // set default animations client->ps.torsoAnim = BOTH_STAND2; client->ps.legsAnim = BOTH_STAND2; // restore some player data if this is a spawn point with KEEP_REV (spawnflags&1) set... // if ( eSavedGameJustLoaded == eAUTO || (spawnPoint->spawnflags&1) || // KEEP_PREV g_qbLoadTransition == qtrue ) { Player_RestoreFromPrevLevel(ent); } // run a client frame to drop exactly to the floor, // initialize animations and other things client->ps.commandTime = level.time - 100; ucmd = client->pers.lastCommand; ucmd.serverTime = level.time; VectorCopy( client->pers.cmd_angles, ucmd.angles ); ClientThink( ent-g_entities, &ucmd ); // run the presend to set anything else ClientEndFrame( ent ); // clear entity state values PlayerStateToEntityState( &client->ps, &ent->s ); //ICARUS include ICARUS_FreeEnt( ent ); //FIXME: This shouldn't need to be done...? ICARUS_InitEnt( ent ); G_InitBoltOnData( ent ); if ( spawnPoint->spawnflags & 64 ) {//player starts with absolutely no weapons ent->client->ps.stats[STAT_WEAPONS] = ( 1 << WP_NONE ); ent->client->ps.ammo[weaponData[WP_NONE].ammoIndex] = 32000; // checkme ent->client->ps.weapon = WP_NONE; ent->client->ps.weaponstate = WEAPON_READY; } { // fire the targets of the spawn point G_UseTargets( spawnPoint, ent ); //Designers needed them to fire off target2's as well... this is kind of messy G_UseTargets2( spawnPoint, ent, spawnPoint->target2 ); /* // 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; } }*/ } } ent->max_health = client->ps.stats[STAT_MAX_HEALTH]; return beamInEffect; } /* =========== ClientDisconnect Called when a player drops from the server. Will not be called between levels. ============ */ void ClientDisconnect( int clientNum ) { gentity_t *ent; ent = g_entities + clientNum; if ( !ent->client ) { return; } // send effect if they were completely connected /* if ( ent->client->pers.connected == CON_CONNECTED ) { gentity_t *tent; 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 ); } */ gi.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; gi.SetConfigstring( CS_PLAYERS + clientNum, ""); }