/* =============== SV_SetConfigstringRestrictions =============== */ void SV_SetConfigstringRestrictions (int index, const clientList_t* clientList) { int i; clientList_t oldClientList = sv.configstrings[index].clientList; sv.configstrings[index].clientList = *clientList; sv.configstrings[index].restricted = qtrue; for ( i = 0 ; i < sv_maxclients->integer ; i++ ) { if ( svs.clients[i].state >= CS_CONNECTED ) { if ( Com_ClientListContains( &oldClientList, i ) != Com_ClientListContains( clientList, i ) ) { // A client has left or joined the restricted list, so update SV_SendConfigstring(&svs.clients[i], index); } } } }
/* =============== SV_SendConfigstring Creates and sends the server command necessary to update the CS index for the given client =============== */ static void SV_SendConfigstring(client_t *client, int index) { int maxChunkSize = MAX_STRING_CHARS - 24; int len; if( sv.configstrings[index].restricted && Com_ClientListContains( &sv.configstrings[index].clientList, client - svs.clients ) ) { // Send a blank config string for this client if it's listed SV_SendServerCommand( client, -1, "cs %i \"\"\n", index ); return; } len = strlen(sv.configstrings[index].s); if( len >= maxChunkSize ) { int sent = 0; int remaining = len; char *cmd; char buf[MAX_STRING_CHARS]; while (remaining > 0 ) { if ( sent == 0 ) { cmd = "bcs0"; } else if( remaining < maxChunkSize ) { cmd = "bcs2"; } else { cmd = "bcs1"; } Q_strncpyz( buf, &sv.configstrings[index].s[sent], maxChunkSize ); SV_SendServerCommand( client, -1, "%s %i \"%s\"\n", cmd, index, buf ); sent += (maxChunkSize - 1); remaining -= (maxChunkSize - 1); } } else { // standard cs, just send it SV_SendServerCommand( client, -1, "cs %i \"%s\"\n", index, sv.configstrings[index].s ); } }
/* ================= CG_DrawPlayerScore ================= */ static void CG_DrawPlayerScore( int y, score_t *score, float *color, float fade, qboolean largeFormat ) { char string[1024]; vec3_t headAngles; playerInfo_t *pi; int iconx, headx; playerState_t *ps; if ( score->playerNum < 0 || score->playerNum >= cgs.maxplayers ) { Com_Printf( "Bad score->playerNum: %i\n", score->playerNum ); return; } pi = &cgs.playerinfo[score->playerNum]; iconx = SB_BOTICON_X + (SB_RATING_WIDTH / 2); headx = SB_HEAD_X + (SB_RATING_WIDTH / 2); // draw the handicap or bot skill marker (unless player has flag) if ( pi->powerups & ( 1 << PW_NEUTRALFLAG ) ) { if( largeFormat ) { CG_DrawFlagModel( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, TEAM_FREE, qfalse ); } else { CG_DrawFlagModel( iconx, y, 16, 16, TEAM_FREE, qfalse ); } } else if ( pi->powerups & ( 1 << PW_REDFLAG ) ) { if( largeFormat ) { CG_DrawFlagModel( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, TEAM_RED, qfalse ); } else { CG_DrawFlagModel( iconx, y, 16, 16, TEAM_RED, qfalse ); } } else if ( pi->powerups & ( 1 << PW_BLUEFLAG ) ) { if( largeFormat ) { CG_DrawFlagModel( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, TEAM_BLUE, qfalse ); } else { CG_DrawFlagModel( iconx, y, 16, 16, TEAM_BLUE, qfalse ); } } else { if ( pi->botSkill > 0 && pi->botSkill <= 5 ) { if ( cg_drawIcons.integer ) { if( largeFormat ) { CG_DrawPic( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, cgs.media.botSkillShaders[ pi->botSkill - 1 ] ); } else { CG_DrawPic( iconx, y, 16, 16, cgs.media.botSkillShaders[ pi->botSkill - 1 ] ); } } } else if ( pi->handicap < 100 ) { Com_sprintf( string, sizeof( string ), "%i", pi->handicap ); if ( cgs.gametype == GT_TOURNAMENT ) { CG_DrawString( iconx, y - SMALLCHAR_HEIGHT/2, string, UI_SMALLFONT|UI_NOSCALE, color ); } else { CG_DrawString( iconx, y, string, UI_SMALLFONT|UI_NOSCALE, color ); } } // draw the wins / losses if ( cgs.gametype == GT_TOURNAMENT ) { Com_sprintf( string, sizeof( string ), "%i/%i", pi->wins, pi->losses ); if( pi->handicap < 100 && !pi->botSkill ) { CG_DrawString( iconx, y + SMALLCHAR_HEIGHT/2, string, UI_SMALLFONT|UI_NOSCALE, color ); } else { CG_DrawString( iconx, y, string, UI_SMALLFONT|UI_NOSCALE, color ); } } } // draw the face VectorClear( headAngles ); headAngles[YAW] = 180; if( largeFormat ) { CG_DrawHead( headx, y - ( ICON_SIZE - BIGCHAR_HEIGHT ) / 2, ICON_SIZE, ICON_SIZE, score->playerNum, headAngles ); } else { CG_DrawHead( headx, y, 16, 16, score->playerNum, headAngles ); } #ifdef MISSIONPACK // draw the team task switch ( pi->teamTask ) { case TEAMTASK_OFFENSE: CG_DrawPic( headx + 48, y, 16, 16, cgs.media.assaultShader ); break; case TEAMTASK_DEFENSE: CG_DrawPic( headx + 48, y, 16, 16, cgs.media.defendShader ); break; case TEAMTASK_PATROL: CG_DrawPic( headx + 48, y, 16, 16, cgs.media.patrolShader ); break; case TEAMTASK_FOLLOW: CG_DrawPic( headx + 48, y, 16, 16, cgs.media.followShader ); break; case TEAMTASK_CAMP: CG_DrawPic( headx + 48, y, 16, 16, cgs.media.campShader ); break; case TEAMTASK_RETRIEVE: CG_DrawPic( headx + 48, y, 16, 16, cgs.media.retrieveShader ); break; case TEAMTASK_ESCORT: CG_DrawPic( headx + 48, y, 16, 16, cgs.media.escortShader ); break; default: break; } #endif if (cg.cur_ps) { if (score->playerNum == cg.cur_ps->playerNum) { ps = cg.cur_ps; } else { ps = NULL; } } else { ps = CG_LocalPlayerState(score->playerNum); } // highlight your position if ( ps ) { float hcolor[4]; int rank; localPlayer = qtrue; if ( ps->persistant[PERS_TEAM] == TEAM_SPECTATOR || cgs.gametype >= GT_TEAM ) { rank = -1; } else { rank = ps->persistant[PERS_RANK] & ~RANK_TIED_FLAG; } if ( rank == 0 ) { hcolor[0] = 0; hcolor[1] = 0; hcolor[2] = 0.7f; } else if ( rank == 1 ) { hcolor[0] = 0.7f; hcolor[1] = 0; hcolor[2] = 0; } else if ( rank == 2 ) { hcolor[0] = 0.7f; hcolor[1] = 0.7f; hcolor[2] = 0; } else { hcolor[0] = 0.7f; hcolor[1] = 0.7f; hcolor[2] = 0.7f; } hcolor[3] = fade * 0.7; CG_FillRect( SB_SCORELINE_X + BIGCHAR_WIDTH + (SB_RATING_WIDTH / 2), y, 640 - SB_SCORELINE_X - BIGCHAR_WIDTH - (SB_RATING_WIDTH / 2), BIGCHAR_HEIGHT+1, hcolor ); } // draw the score line if ( score->ping == -1 ) { Com_sprintf(string, sizeof(string), "connecting"); } else if ( pi->team == TEAM_SPECTATOR ) { Com_sprintf(string, sizeof(string), "SPECT"); } else { Com_sprintf(string, sizeof(string), "%5i", score->score); } CG_DrawString( SB_SCORE_X + (SB_RATING_WIDTH / 2) + 4*BIGCHAR_WIDTH, y, string, UI_RIGHT|UI_DROPSHADOW|UI_BIGFONT|UI_NOSCALE, color ); if ( score->ping != -1 ) { Com_sprintf(string, sizeof(string), "%4i", score->ping); CG_DrawString( SB_PING_X - (SB_RATING_WIDTH / 2) + 4*BIGCHAR_WIDTH, y, string, UI_RIGHT|UI_DROPSHADOW|UI_BIGFONT|UI_NOSCALE, color ); Com_sprintf(string, sizeof(string), "%4i", score->time); CG_DrawString( SB_TIME_X - (SB_RATING_WIDTH / 2) + 4*BIGCHAR_WIDTH, y, string, UI_RIGHT|UI_DROPSHADOW|UI_BIGFONT|UI_NOSCALE, color ); } CG_DrawString( SB_NAME_X - (SB_RATING_WIDTH / 2), y, pi->name, UI_LEFT|UI_DROPSHADOW|UI_BIGFONT|UI_NOSCALE, color ); // add the "ready" marker for intermission exiting if ( Com_ClientListContains( &cg.readyPlayers, score->playerNum ) ) { CG_DrawString( iconx, y, "READY", UI_LEFT|UI_DROPSHADOW|UI_BIGFONT|UI_NOSCALE, color ); } }
/* =============== SV_AddEntitiesVisibleFromPoint =============== */ static void SV_AddEntitiesVisibleFromPoint( int psIndex, int clientNum, vec3_t origin, clientSnapshot_t *frame, snapshotEntityNumbers_t *eNums, qboolean portal ) { int e, i; sharedEntity_t *ent; svEntity_t *svEnt; int l; int clientarea, clientcluster; int leafnum; byte *clientpvs; byte *bitvector; // during an error shutdown message we may need to transmit // the shutdown message after the server has shutdown, so // specfically check for it if ( !sv.state ) { return; } leafnum = CM_PointLeafnum (origin); clientarea = CM_LeafArea (leafnum); clientcluster = CM_LeafCluster (leafnum); // calculate the visible areas frame->areabytes[psIndex] = CM_WriteAreaBits( frame->areabits[psIndex], clientarea ); clientpvs = CM_ClusterPVS (clientcluster); for ( e = 0 ; e < sv.num_entities ; e++ ) { ent = SV_GentityNum(e); // never send entities that aren't linked in if ( !ent->r.linked ) { continue; } if (ent->s.number != e) { Com_DPrintf ("FIXING ENT->S.NUMBER!!!\n"); ent->s.number = e; } // entities can be flagged to explicitly not be sent to the client if ( ent->r.svFlags & SVF_NOCLIENT ) { continue; } // entities can be flagged to be sent to a given mask of clients if ( ent->r.svFlags & SVF_CLIENTMASK ) { if ( !Com_ClientListContains( &ent->r.sendClients, clientNum ) ) continue; } svEnt = SV_SvEntityForGentity( ent ); // don't double add an entity through portals if ( svEnt->snapshotCounter == sv.snapshotCounter ) { continue; } // limit based on distance if ( ent->r.cullDistance ) { vec3_t dir; VectorSubtract(ent->s.origin, origin, dir); if ( VectorLengthSquared(dir) > (float) ent->r.cullDistance * ent->r.cullDistance ) { continue; } } // broadcast entities are always sent if ( ent->r.svFlags & SVF_BROADCAST ) { SV_AddEntToSnapshot( frame, svEnt, ent, eNums ); continue; } // ignore if not touching a PV leaf // check area if ( !CM_AreasConnected( clientarea, svEnt->areanum ) ) { // doors can legally straddle two areas, so // we may need to check another one if ( !CM_AreasConnected( clientarea, svEnt->areanum2 ) ) { continue; // blocked by a door } } bitvector = clientpvs; // check individual leafs if ( !svEnt->numClusters ) { continue; } l = 0; for ( i=0 ; i < svEnt->numClusters ; i++ ) { l = svEnt->clusternums[i]; if ( bitvector[l >> 3] & (1 << (l&7) ) ) { break; } } // if we haven't found it to be visible, // check overflow clusters that coudln't be stored if ( i == svEnt->numClusters ) { if ( svEnt->lastCluster ) { for ( ; l <= svEnt->lastCluster ; l++ ) { if ( bitvector[l >> 3] & (1 << (l&7) ) ) { break; } } if ( l == svEnt->lastCluster ) { continue; // not visible } } else { continue; } } // visibility dummies if ( ent->r.svFlags & SVF_VISDUMMY ) { sharedEntity_t *ment = NULL; // find master ment = SV_GentityNum( ent->r.visDummyNum ); if ( ment ) { svEntity_t *master = NULL; master = SV_SvEntityForGentity( ment ); if ( master->snapshotCounter == sv.snapshotCounter || !ment->r.linked ) { continue; } SV_AddEntToSnapshot( frame, master, ment, eNums ); } // master needs to be added, but not this dummy ent continue; } else if ( ent->r.svFlags & SVF_VISDUMMY_MULTIPLE ) { int h; sharedEntity_t *ment = NULL; svEntity_t *master = NULL; for ( h = 0; h < sv.num_entities; h++ ) { ment = SV_GentityNum( h ); if ( ment == ent ) { continue; } if ( ment ) { master = SV_SvEntityForGentity( ment ); } else { continue; } if ( !ment->r.linked ) { continue; } if ( ment->s.number != h ) { Com_DPrintf( "FIXING vis dummy multiple ment->S.NUMBER!!!\n" ); ment->s.number = h; } if ( ment->r.svFlags & SVF_NOCLIENT ) { continue; } if ( master->snapshotCounter == sv.snapshotCounter ) { continue; } if ( ment->r.visDummyNum == ent->s.number ) { SV_AddEntToSnapshot( frame, master, ment, eNums ); } } // masters need to be added, but not this dummy ent continue; } // add it SV_AddEntToSnapshot( frame, svEnt, ent, eNums ); // if it's a portal entity, add everything visible from its camera position if ( ent->r.svFlags & SVF_PORTAL ) { if ( ent->r.portalCullDistance ) { vec3_t dir; VectorSubtract(ent->s.origin, origin, dir); if ( VectorLengthSquared(dir) > (float) ent->r.portalCullDistance * ent->r.portalCullDistance ) { continue; } } SV_AddEntitiesVisibleFromPoint( psIndex, clientNum, ent->s.origin2, frame, eNums, qtrue ); } }
/* ================= CG_Say ================= */ static void CG_Say( const char *name, int clientNum, saymode_t mode, const char *text ) { char prefix[ 21 ] = ""; char *ignore = ""; char *location = ""; char color; char *maybeColon; if ( clientNum >= 0 && clientNum < MAX_CLIENTS ) { clientInfo_t *ci = &cgs.clientinfo[ clientNum ]; char *tcolor = S_COLOR_WHITE; name = ci->name; if ( ci->team == TEAM_ALIENS ) { tcolor = S_COLOR_RED; } else if ( ci->team == TEAM_HUMANS ) { tcolor = S_COLOR_CYAN; } if ( cg_chatTeamPrefix.integer ) { Com_sprintf( prefix, sizeof( prefix ), "[%s%c" S_COLOR_WHITE "] ", tcolor, toupper( * ( BG_TeamName( ci->team ) ) ) ); } if ( Com_ClientListContains( &cgs.ignoreList, clientNum ) ) { ignore = "[skipnotify]"; } if ( ( mode == SAY_TEAM || mode == SAY_AREA ) && cg.snap->ps.pm_type != PM_INTERMISSION ) { int locationNum; if ( clientNum == cg.snap->ps.clientNum ) { centity_t *locent; locent = CG_GetPlayerLocation(); if ( locent ) { locationNum = locent->currentState.generic1; } else { locationNum = 0; } } else { locationNum = ci->location; } if ( locationNum > 0 && locationNum < MAX_LOCATIONS ) { const char *s = CG_ConfigString( CS_LOCATIONS + locationNum ); if ( *s ) { location = va( " (%s" S_COLOR_WHITE ")", s ); } } } } else if ( name ) { Q_strcat( prefix, sizeof( prefix ), "[ADMIN]" ); } else { name = "console"; } // IRC-like /me parsing if ( mode != SAY_RAW && Q_stricmpn( text, "/me ", 4 ) == 0 ) { text += 4; Q_strcat( prefix, sizeof( prefix ), "* " ); maybeColon = ""; } else { maybeColon = ":"; } color = '0' + UI_GetChatColour( mode, cgs.clientinfo[ clientNum ].team ); switch ( mode ) { case SAY_ALL: // might already be ignored but in that case no harm is done if ( cg_teamChatsOnly.integer ) { ignore = "[skipnotify]"; } case SAY_ALL_ADMIN: CG_Printf( "%s%s%s^7%s ^%c%s\n", ignore, prefix, name, maybeColon, color, text ); break; case SAY_TEAM: CG_Printf( "%s%s(%s^7)%s%s ^%c%s\n", ignore, prefix, name, location, maybeColon, color, text ); break; case SAY_ADMINS: case SAY_ADMINS_PUBLIC: CG_Printf( "%s%s%s%s^7%s ^%c%s\n", ignore, prefix, ( mode == SAY_ADMINS ) ? "[ADMIN]" : "[PLAYER]", name, maybeColon, color, text ); break; case SAY_AREA: case SAY_AREA_TEAM: CG_Printf( "%s%s<%s^7>%s%s ^%c%s\n", ignore, prefix, name, location, maybeColon, color, text ); break; case SAY_PRIVMSG: case SAY_TPRIVMSG: CG_Printf( "%s%s[%s^7 -> %s^7]%s ^%c%s\n", ignore, prefix, name, cgs.clientinfo[ cg.clientNum ].name, maybeColon, color, text ); if ( !ignore[ 0 ] ) { CG_CenterPrint( va( "^%cPrivate message from: " S_COLOR_WHITE "%s", color, name ), 200, GIANTCHAR_WIDTH * 4 ); if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { clientNum = cg.clientNum; } CG_Printf(_( ">> to reply, say: /m %d [your message] <<\n"), clientNum ); } break; case SAY_RAW: CG_Printf( "%s\n", text ); break; case SAY_DEFAULT: default: break; } switch ( mode ) { case SAY_TEAM: case SAY_AREA: case SAY_TPRIVMSG: if ( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) { trap_S_StartLocalSound( cgs.media.alienTalkSound, CHAN_LOCAL_SOUND ); break; } else if ( cg.snap->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) { trap_S_StartLocalSound( cgs.media.humanTalkSound, CHAN_LOCAL_SOUND ); break; } default: trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); } }
/* ================= CG_ParseVoice voice clientNum vChan cmdNum trackNum [sayText] ================= */ static void CG_ParseVoice( void ) { int clientNum; voiceChannel_t vChan; char sayText[ MAX_SAY_TEXT ] = { "" }; voiceTrack_t *track; clientInfo_t *ci; if ( trap_Argc() < 5 || trap_Argc() > 6 ) { return; } if ( trap_Argc() == 6 ) { Q_strncpyz( sayText, CG_Argv( 5 ), sizeof( sayText ) ); } clientNum = atoi( CG_Argv( 1 ) ); if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { return; } vChan = atoi( CG_Argv( 2 ) ); if ( ( unsigned ) vChan >= VOICE_CHAN_NUM_CHANS ) { return; } if ( cg_teamChatsOnly.integer && vChan != VOICE_CHAN_TEAM ) { return; } ci = &cgs.clientinfo[ clientNum ]; // this joker is still talking if ( ci->voiceTime > cg.time ) { return; } track = CG_VoiceTrack( ci->voice, atoi( CG_Argv( 3 ) ), atoi( CG_Argv( 4 ) ) ); // keep track of how long the player will be speaking // assume it takes 3s to say "*unintelligible gibberish*" if ( track ) { ci->voiceTime = cg.time + track->duration; } else { ci->voiceTime = cg.time + 3000; } if ( !sayText[ 0 ] ) { if ( track ) { Q_strncpyz( sayText, track->text, sizeof( sayText ) ); } else { Q_strncpyz( sayText, "*unintelligible gibberish*", sizeof( sayText ) ); } } if ( !cg_noVoiceText.integer ) { switch ( vChan ) { case VOICE_CHAN_ALL: CG_Say( NULL, clientNum, SAY_ALL, sayText ); break; case VOICE_CHAN_TEAM: CG_Say( NULL, clientNum, SAY_TEAM, sayText ); break; case VOICE_CHAN_LOCAL: CG_Say( NULL, clientNum, SAY_AREA_TEAM, sayText ); break; default: break; } } // playing voice audio tracks disabled if ( cg_noVoiceChats.integer ) { return; } // no audio track to play if ( !track ) { return; } // don't play audio track for lamers if ( Com_ClientListContains( &cgs.ignoreList, clientNum ) ) { return; } switch ( vChan ) { case VOICE_CHAN_ALL: trap_S_StartLocalSound( track->track, CHAN_VOICE ); break; case VOICE_CHAN_TEAM: trap_S_StartLocalSound( track->track, CHAN_VOICE ); break; case VOICE_CHAN_LOCAL: trap_S_StartSound( NULL, clientNum, CHAN_VOICE, track->track ); break; default: break; } }