void SV_WriteDemoArchive(client_t *client){ byte bufData[72]; msg_t msg; MSG_Init(&msg, bufData, sizeof(bufData)); int archiveIndex; playerState_t *ps = SV_GameClientNum(client - svs.clients); vec3_t nullvec = {0, 0, 0}; MSG_WriteByte(&msg, 1); archiveIndex = client->demoArchiveIndex % 256; MSG_WriteLong(&msg, archiveIndex); MSG_WriteVector(&msg, ps->origin); MSG_WriteVector(&msg, nullvec); MSG_WriteLong(&msg, 0); //Velocity MSG_WriteLong(&msg, 0); MSG_WriteLong(&msg, ps->commandTime); MSG_WriteVector(&msg, ps->viewangles); client->demoArchiveIndex++; FS_DemoWrite( msg.data, msg.cursize, &client->demofile ); }
/** * @brief Creates client information for other functions * @param clientNum Client ID (from 0 to MAX_CLIENTS) * @note Just for sv_trackbase.c internal use */ char *Tracker_createClientInfo(int clientNum) { playerState_t *ps; ps = SV_GameClientNum(clientNum); return va("%i\\%i\\%c\\%i\\%s", svs.clients[clientNum].ping, ps->persistant[PERS_SCORE], Info_ValueForKey(Cvar_InfoString(CVAR_SERVERINFO | CVAR_SERVERINFO_NOUPDATE), "P")[clientNum], ps->stats[STAT_PLAYER_CLASS], svs.clients[clientNum].name); }
/* ================ SV_Status_f ================ */ static void SV_Status_f( void ) { int i, j, l; client_t *cl; playerState_t *ps; const char *s; int ping; // make sure server is running if ( !com_sv_running->integer ) { Com_Printf( "Server is not running.\n" ); return; } Com_Printf ("map: %s\n", sv_mapname->string ); Com_Printf ("num score ping name lastmsg address qport rate\n"); Com_Printf ("--- ----- ---- --------------- ------- --------------------- ----- -----\n"); for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { if (!cl->state) continue; Com_Printf ("%3i ", i); ps = SV_GameClientNum( i ); Com_Printf ("%5i ", ps->persistant[PERS_SCORE]); if (cl->state == CS_CONNECTED) Com_Printf ("CNCT "); else if (cl->state == CS_ZOMBIE) Com_Printf ("ZMBI "); else { ping = cl->ping < 9999 ? cl->ping : 9999; Com_Printf ("%4i ", ping); } Com_Printf ("%s", cl->name); // TTimo adding a ^7 to reset the color // NOTE: colored names in status breaks the padding (WONTFIX) Com_Printf ("^7"); l = 16 - strlen(cl->name); for (j=0 ; j<l ; j++) Com_Printf (" "); Com_Printf ("%7i ", svs.time - cl->lastPacketTime ); s = NET_AdrToString( cl->netchan.remoteAddress ); Com_Printf ("%s", s); l = 22 - strlen(s); for (j=0 ; j<l ; j++) Com_Printf (" "); Com_Printf ("%5i", cl->netchan.qport); Com_Printf (" %5i", cl->rate); Com_Printf ("\n"); } Com_Printf ("\n"); }
/* =================== SV_CalcPings Updates the cl->ping variables =================== */ void SV_CalcPings( void ) { int i, j; client_t *cl; int total, count; int delta; playerState_t *ps; for ( i = 0 ; i < sv_maxclients->integer ; i++ ) { cl = &svs.clients[i]; #if defined RTCW_MP // DHM - Nerve #ifdef UPDATE_SERVER if ( !cl ) { continue; } #endif #endif // RTCW_XX if ( cl->state != CS_ACTIVE ) { cl->ping = 999; continue; } if ( !cl->gentity ) { cl->ping = 999; continue; } if ( cl->gentity->r.svFlags & SVF_BOT ) { cl->ping = 0; continue; } total = 0; count = 0; for ( j = 0 ; j < PACKET_BACKUP ; j++ ) { if ( cl->frames[j].messageAcked <= 0 ) { continue; } delta = cl->frames[j].messageAcked - cl->frames[j].messageSent; count++; total += delta; } if ( !count ) { cl->ping = 999; } else { cl->ping = total / count; if ( cl->ping > 999 ) { cl->ping = 999; } } // let the game dll know about the ping ps = SV_GameClientNum( i ); ps->ping = cl->ping; } }
/* ================ SVC_Status Responds with all the info that qplug or qspy can see about the server and all connected players. Used for getting detailed information after the simple info query. ================ */ void SVC_Status( netadr_t from ) { char player[1024]; char status[MAX_MSGLEN]; int i; client_t *cl; playerState_t *ps; int statusLength; int playerLength; char infostring[MAX_INFO_STRING]; // ignore if we are in single player if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER ) { return; } // DHM - Nerve #ifdef UPDATE_SERVER return; #endif strcpy( infostring, Cvar_InfoString( CVAR_SERVERINFO ) ); // echo back the parameter to status. so master servers can use it as a challenge // to prevent timed spoofed reply packets that add ghost servers Info_SetValueForKey( infostring, "challenge", Cmd_Argv( 1 ) ); // add "demo" to the sv_keywords if restricted if ( Cvar_VariableValue( "fs_restrict" ) ) { char keywords[MAX_INFO_STRING]; Com_sprintf( keywords, sizeof( keywords ), "demo %s", Info_ValueForKey( infostring, "sv_keywords" ) ); Info_SetValueForKey( infostring, "sv_keywords", keywords ); } status[0] = 0; statusLength = 0; for ( i = 0 ; i < sv_maxclients->integer ; i++ ) { cl = &svs.clients[i]; if ( cl->state >= CS_CONNECTED ) { ps = SV_GameClientNum( i ); Com_sprintf( player, sizeof( player ), "%i %i \"%s\"\n", ps->persistant[PERS_SCORE], cl->ping, cl->name ); playerLength = strlen( player ); if ( statusLength + playerLength >= sizeof( status ) ) { break; // can't hold any more } strcpy( status + statusLength, player ); statusLength += playerLength; } } NET_OutOfBandPrint( NS_SERVER, from, "statusResponse\n%s\n%s", infostring, status ); }
/* ==================== SV_ClientStatus_f Joe Kari: New admin command that provide tons of information about everyone. ==================== */ static void SV_ClientStatus_f( void ) { int i; client_t *cl; const char *s; playerState_t *ps; int ping; // Move to g_svcmds.c // make sure server is running if ( !com_sv_running->integer ) { Com_Printf( "Server is not running.\n" ); return; } for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { if (!cl->state) continue; ps = SV_GameClientNum( i ); // ID Com_Printf ("^5*** ID^7:%i ", i); // Name Com_Printf ("^5name^7:%s^7\n", cl->name); // IP (or: loopback or bot) s = NET_AdrToString( cl->netchan.remoteAddress ); Com_Printf ("^3IP^7:%s ", s); // GUID s = Info_ValueForKey (cl->userinfo, "cl_guid"); if (Q_stricmp (s, "") == 0) s = "<null>" ; Com_Printf ("^3GUID^7:%s\n", s); // Ping if (cl->state == CS_CONNECTED) Com_Printf ("^6ping^7:CONNECTED "); else if (cl->state == CS_ZOMBIE) Com_Printf ("^6ping^7:ZOMBIE "); else { ping = cl->ping < 9999 ? cl->ping : 9999; Com_Printf ("^6ping^7:%i ", ping); } // Rate Com_Printf ("^6rate^7:%i ", cl->rate); // Port Com_Printf ("^6port^7:%i\n", cl->netchan.qport); } }
/* ================ SVC_Status Responds with all the info that qplug or qspy can see about the server and all connected players. Used for getting detailed information after the simple info query. ================ */ void SVC_Status( netadr_t from, const Cmd::Args& args ) { char player[ 1024 ]; char status[ MAX_MSGLEN ]; int i; client_t *cl; playerState_t *ps; int statusLength; int playerLength; char infostring[ MAX_INFO_STRING ]; //bani - bugtraq 12534 if ( args.Argc() > 1 && !SV_VerifyChallenge( args.Argv(1).c_str() ) ) { return; } Q_strncpyz( infostring, Cvar_InfoString( CVAR_SERVERINFO, false ), MAX_INFO_STRING ); if ( args.Argc() > 1 ) { // echo back the parameter to status. so master servers can use it as a challenge // to prevent timed spoofed reply packets that add ghost servers Info_SetValueForKey( infostring, "challenge", args.Argv(1).c_str(), false ); } status[ 0 ] = 0; statusLength = 0; for ( i = 0; i < sv_maxclients->integer; i++ ) { cl = &svs.clients[ i ]; if ( cl->state >= CS_CONNECTED ) { ps = SV_GameClientNum( i ); Com_sprintf( player, sizeof( player ), "%i %i \"%s\"\n", ps->persistant[ PERS_SCORE ], cl->ping, cl->name ); playerLength = strlen( player ); if ( statusLength + playerLength >= (int) sizeof( status ) ) { break; // can't hold any more } strcpy( status + statusLength, player ); statusLength += playerLength; } } NET_OutOfBandPrint( NS_SERVER, from, "statusResponse\n%s\n%s", infostring, status ); }
static void SV_NetStatus_f(void) { char netStatus[4096] = {0}; int i; char cleanName[MAX_NETNAME] = {0}; client_t *cl; playerState_t *ps; Q_strcat(netStatus, sizeof(netStatus), "\n"); Q_strcat(netStatus, sizeof(netStatus), "ID Score Ping Name LastMSG Address QPort Rate\n"); Q_strcat(netStatus, sizeof(netStatus), "-- ----- ---- --------------- ------- --------------------- ----- -----\n"); for (i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++) { if (!cl->state) { continue; } Q_strcat(netStatus, sizeof(netStatus), va("%2i ", i)); //ID ps = SV_GameClientNum(i); Q_strcat(netStatus, sizeof(netStatus), va("%5i ", ps->persistant[PERS_SCORE])); //Score if (cl->state == CS_CONNECTED) Q_strcat(netStatus, sizeof(netStatus), "CNCT "); //Ping else if (cl->state == CS_ZOMBIE) Q_strcat(netStatus, sizeof(netStatus), "ZMBI "); //Ping else Q_strcat(netStatus, sizeof(netStatus), va("%4i ", cl->ping < 999 ? cl->ping : 999)); //Ping //FIXME: stripcolor until we fix alignment Q_strncpyz(cleanName, cl->name, sizeof(cleanName)); Q_StripColor(cleanName); Q_strcat(netStatus, sizeof(netStatus), va("%-15.15s ", cleanName)); //Name Q_strcat(netStatus, sizeof(netStatus), va("%7i ", svs.time - cl->lastPacketTime)); //LastMSG Q_strcat(netStatus, sizeof(netStatus), va("%21s ", NET_AdrToString(cl->netchan.remoteAddress))); //Address Q_strcat(netStatus, sizeof(netStatus), va("%5i ", cl->netchan.qport)); //QPort Q_strcat(netStatus, sizeof(netStatus), va("%5i ", cl->rate)); //Rate } Q_strcat(netStatus, sizeof(netStatus), "\n"); Com_Printf(netStatus); }
/* =================== SV_CalcPings Updates the cl->ping variables =================== */ void SV_CalcPings( void ) { int i, j; client_t *cl; int total, count; int delta; playerState_t *ps; for (i=0 ; i < sv_maxclients->integer ; i++) { cl = &svs.clients[i]; if ( cl->state != CS_ACTIVE ) { cl->ping = 999; continue; } if ( !cl->gentity ) { cl->ping = 999; continue; } total = 0; count = 0; for ( j = 0 ; j < PACKET_BACKUP ; j++ ) { if ( cl->frames[j].messageAcked <= 0 ) { continue; } delta = cl->frames[j].messageAcked - cl->frames[j].messageSent; count++; total += delta; } if (!count) { cl->ping = 999; } else { cl->ping = total/count; if ( cl->ping > 999 ) { cl->ping = 999; } } // let the game dll know about the ping ps = SV_GameClientNum( i ); ps->ping = cl->ping; } }
void SV_WriteDemoArchive(msg_t *msg, client_t *client){ int archiveIndex; playerState_t *ps = SV_GameClientNum(client - svs.clients); vec3_t nullvec = {0, 0, 0}; MSG_WriteByte(msg, 1); archiveIndex = client->demoArchiveIndex % 256; MSG_WriteLong(msg, archiveIndex); MSG_WriteVector(msg, ps->origin); MSG_WriteVector(msg, nullvec); MSG_WriteLong(msg, 0); //Velocity MSG_WriteLong(msg, 0); MSG_WriteLong(msg, ps->commandTime); MSG_WriteVector(msg, ps->viewangles); client->demoArchiveIndex++; }
/* ================ SV_Status_f ================ */ static void SV_Status_f(void) { int i, j, l; client_t *cl; playerState_t *ps; const char *s; int ping; // make sure server is running if (!com_sv_running->integer) { Com_Printf("Server is not running.\n"); return; } Com_Printf("cpu server utilization: %i %%\n" "avg response time : %i ms\n" "server time : %i\n" "internal time : %i\n" "map : %s\n\n" "num score ping name lastmsg address qport rate\n" "--- ----- ---- --------------- ------- --------------------- ----- -----\n", ( int ) svs.stats.cpu, ( int ) svs.stats.avg, svs.time, Sys_Milliseconds(), sv_mapname->string); // FIXME: extend player name length (>16 chars) ? - they are printed! // FIXME: do a Com_Printf per line! ... create the row at first for (i = 0, cl = svs.clients ; i < sv_maxclients->integer ; i++, cl++) { if (!cl->state && !cl->demoClient) { continue; } Com_Printf("%3i ", i); ps = SV_GameClientNum(i); Com_Printf("%5i ", ps->persistant[PERS_SCORE]); if (cl->demoClient) // if it's a democlient, we show DEMO instead of the ping (which would be 999 anyway - which is not the case in the scoreboard which show the real ping that had the player because commands are replayed!) { Com_Printf("DEMO "); } else if (cl->state == CS_CONNECTED) { Com_Printf("CNCT "); } else if (cl->state == CS_ZOMBIE) { Com_Printf("ZMBI "); } else { ping = cl->ping < 9999 ? cl->ping : 9999; Com_Printf("%4i ", ping); } Com_Printf("%s", rc(cl->name)); l = 16 - strlen(cl->name); for (j = 0 ; j < l ; j++) { Com_Printf(" "); } Com_Printf("%7i ", svs.time - cl->lastPacketTime); s = NET_AdrToString(cl->netchan.remoteAddress); Com_Printf("%s", s); l = 22 - strlen(s); for (j = 0 ; j < l ; j++) { Com_Printf(" "); } Com_Printf("%5i", cl->netchan.qport); Com_Printf(" %5i\n", cl->rate); } Com_Printf("\n"); }
/* ================ SV_Status_f ================ */ static void SV_Status_f( void ) { int i, humans, bots; client_t *cl; playerState_t *ps; const char *s; int ping; char state[32]; qboolean avoidTruncation = qfalse; // make sure server is running if ( !com_sv_running->integer ) { //Ensiform: Why raven why, you didn't do this in other cmds. Com_Printf( "Server is not running.\n" ); //Com_Printf( SE_GetString("STR_SERVER_SERVER_NOT_RUNNING") ); return; } if ( Cmd_Argc() > 1 ) { if (!Q_stricmp("notrunc", Cmd_Argv(1))) { avoidTruncation = qtrue; } } humans = bots = 0; for ( i = 0 ; i < sv_maxclients->integer ; i++ ) { if ( svs.clients[i].state >= CS_CONNECTED ) { if ( svs.clients[i].netchan.remoteAddress.type != NA_BOT ) { humans++; } else { bots++; } } } #if defined(_WIN32) #define STATUS_OS "Windows" #elif defined(__linux__) #define STATUS_OS "Linux" #elif defined(MACOS_X) #define STATUS_OS "OSX" #else #define STATUS_OS "Unknown" #endif const char *ded_table[] = { "listen", "lan dedicated", "public dedicated", }; Com_Printf ("hostname: %s\n", sv_hostname->string ); Com_Printf ("version : %s %i\n", VERSION_STRING_DOTTED, PROTOCOL_VERSION ); Com_Printf ("game : %s\n", FS_GetCurrentGameDir() ); Com_Printf ("udp/ip : %s:%i os(%s) type(%s)\n", Cvar_VariableString("net_ip"), Cvar_VariableIntegerValue("net_port"), STATUS_OS, ded_table[com_dedicated->integer]); Com_Printf ("map : %s gametype(%i)\n", sv_mapname->string, sv_gametype->integer ); Com_Printf ("players : %i humans, %i bots (%i max)\n", humans, bots, sv_maxclients->integer - sv_privateClients->integer); Com_Printf ("num score ping name lastmsg address qport rate\n"); Com_Printf ("--- ----- ---- --------------- ------- --------------------- ----- -----\n"); for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { if (!cl->state) { continue; } if (cl->state == CS_CONNECTED) { strcpy(state, "CNCT "); } else if (cl->state == CS_ZOMBIE) { strcpy(state, "ZMBI "); } else { ping = cl->ping < 9999 ? cl->ping : 9999; Com_sprintf(state, sizeof(state), "%4i", ping); } ps = SV_GameClientNum( i ); s = NET_AdrToString( cl->netchan.remoteAddress ); if (!avoidTruncation) { Com_Printf ("%3i %5i %s %-15.15s %7i %21s %5i %5i\n", i, ps->persistant[PERS_SCORE], state, cl->name, svs.time - cl->lastPacketTime, s, cl->netchan.qport, cl->rate ); } else { Com_Printf ("%3i %5i %s %s %7i %21s %5i %5i\n", i, ps->persistant[PERS_SCORE], state, cl->name, svs.time - cl->lastPacketTime, s, cl->netchan.qport, cl->rate ); } } Com_Printf ("\n"); }
/* * Tests whether the player entity ent is visible from the point origin. */ qboolean SV_IsPlayerVisibleFromPoint( vec3_t origin, clientSnapshot_t *frame, sharedEntity_t *ent, vec3_t diff) { int i,contents_mask,goal_ent,viewer_clnum,ent_clnum,tries; trace_t tr; vec3_t start,end,dir,entangles,angles,temp,forward; sharedEntity_t *viewer_ent; client_t *viewer_cl; // client_t *ent_cl; playerState_t *viewer_ps, *ent_ps; float pitch; viewer_clnum = frame->ps.clientNum; // get the client number of the viewer ent_clnum = ent->s.clientNum; // get the client number of the other player if (viewer_clnum == ent_clnum) { // in case the viewer is the player entity return qtrue; // we don't need to hide us from ourselves } viewer_ps = &frame->ps; ent_ps = SV_GameClientNum(ent_clnum); if (viewer_ps->pm_type != PM_NORMAL) { // if the viewer is dead or spectating return qtrue; // let every entity be visible } if (ent_ps->pm_type != PM_NORMAL || (ent->s.weapon == WP_NONE)) { // if the player entity is dead or spectating return qtrue; } viewer_cl = svs.clients+viewer_clnum; // get the client of the viewer // ent_cl = svs.clients+ent_clnum; // get the client of the other player // if (viewer_clnum > ent_clnum) { // if viewer_clnum > ent_clnum, we have already tested whether ent_clnum is able to see viewer_clnum. // if (ent_cl->tracetimer[viewer_clnum] > sv.time) return qtrue; // and we could assume symmetry of SV_IsPlayerVisibleFromPoint // } if (viewer_cl->tracetimer[ent_clnum] > sv.time+MEMORY+10) { // if the sv.time has been reset viewer_cl->tracetimer[ent_clnum] = sv.time; // reset the tracetimer } else if (viewer_cl->tracetimer[ent_clnum] > (sv.time+MEMORY-10)) { // if we have recently seen this entity, we are lazy and assume it is still visible // Com_Printf(va("client: %i, seen: %i\n", ent_clnum, viewer_cl->tracetimer[ent_clnum])); return qtrue; } goal_ent = SV_NumForGentity(ent); // this might always be the same as ent_clnum viewer_ent = SV_GentityNum(viewer_clnum); contents_mask = CONTENTS_SOLID;// |CONTENTS_BODY will work for doors, but also for windows |CONTENTS_PLAYERCLIP|CONTENTS_SOLID|CONTENTS_MOVER|CONTENTS_PLAYERCLIP // if (seen->v.movetype == MOVETYPE_PUSH ) { //don't cull doors and plats :( // return false; // } // if (sv_antiwallhack.value == 1) //1 only check player models, 2 = check all ents // if (strcmp(pr_strings + seen->v.classname, "player")) // return qfalse; // get camera origin (according to \cg_drawdebug 1) start[0] = origin[0]; start[1] = origin[1]; start[2] = origin[2]+3.0f; VectorCopy(viewer_ps->viewangles, angles); AnglesNormalize180(angles); pitch = angles[PITCH]; angles[PITCH] = 0; angles[ROLL] = 0; AngleVectors(angles, forward, NULL, NULL); VectorScale(forward, (pitch/3.5f), temp); VectorAdd( start, temp, start); // if there is sufficient distance between viewer and player entity, check if player entity is within viewer's field of vision VectorSubtract(ent->r.currentOrigin, start, dir); // VectorAdd(ent->r.currentOrigin,dir,diff);// fill diff VectorCopy(viewer_ent->s.pos.trBase,diff);// fill diff vectoangles(dir, entangles); dir[2]=0; // pretend, players are on the same level (the height should no be taken into account) if (VectorLength(dir) > 1024) {// if it is not within close range (x,y-wise, not z-wise) if (!InFieldOfVision(viewer_ps->viewangles, 60.f, entangles, ent_clnum)) {// If the player entity is not in the field of vision of the viewer // Com_Printf( va("behind: %i vorg: %f,%f,%f vang: %f,%f,%f eorg: %f,%f,%f dir: %f,%f,%f eang: %f,%f,%f ent: %i\n", viewer_clnum,origin[0],origin[1],origin[2],viewer_ps->viewangles[0],viewer_ps->viewangles[1],viewer_ps->viewangles[2],ent->r.currentOrigin[0],ent->r.currentOrigin[1],ent->r.currentOrigin[2],dir[0],dir[1],dir[2],entangles[0],entangles[1],entangles[2],ent_clnum)); return qtrue; // if the player entity is behind the viewer, abstain from any computations (and transmit the entity to hear sounds) // } else { // Com_Printf( va("front: %i vorg: %f,%f,%f vang: %f,%f,%f eorg: %f,%f,%f dir: %f,%f,%f eang: %f,%f,%f ent: %i\n", viewer_clnum,origin[0],origin[1],origin[2],viewer_ps->viewangles[0],viewer_ps->viewangles[1],viewer_ps->viewangles[2],ent->r.currentOrigin[0],ent->r.currentOrigin[1],ent->r.currentOrigin[2],dir[0],dir[1],dir[2],entangles[0],entangles[1],entangles[2],ent_clnum)); } } // aim straight at the head of the entity from our eyes end[0] = ent->r.currentOrigin[0]; end[1] = ent->r.currentOrigin[1]; end[2] = ent->r.currentOrigin[2]+ent->r.maxs[2];// "+3.0f" doesn't do it. "+ent->r.maxs[2]" is at the top of the BBox VectorCopy(ent_ps->viewangles, angles); AnglesNormalize180(angles); pitch = angles[PITCH]; angles[PITCH] = 0; angles[ROLL] = 0; AngleVectors(angles, forward, NULL, NULL); VectorScale(forward, (pitch/3.5f), temp); VectorAdd( end, temp, end); memset (&tr, 0, sizeof(tr)); tr.fraction = 1; // check the head SV_Trace(&tr, start, NULL, NULL, end, viewer_clnum, contents_mask, qfalse); // Com_Printf(va("client: %i, trace: %f\n", ent->s.clientNum, tr.fraction)); if (tr.fraction == 1 || tr.entityNum==goal_ent) {// tr.fraction == 1 || if there is a line of sight to the entity viewer_cl->tracetimer[ent_clnum] = sv.time + MEMORY;// update the trace timer so the entity will be visible the next 200 time units return qtrue;// and signal the entity is visible } // Com_Printf(va("origin(%f %f %f) min(%f %f %f) max(%f %f %f)\n", start[0], start[1], start[2], mins[0],mins[1],mins[2],maxs[0],maxs[1],maxs[2])); // check the last good offset VectorAdd(ent->r.currentOrigin,viewer_cl->lasttrace[ent_clnum],end); SV_Trace(&tr, start, NULL, NULL, end, viewer_clnum, contents_mask, qfalse); // Com_Printf(va("client: %i, trace: %f\n", ent->s.clientNum, tr.fraction)); if (tr.fraction == 1 || tr.entityNum==goal_ent) {// tr.fraction == 1 || if there is a line of sight to the entity viewer_cl->tracetimer[ent_clnum] = sv.time + MEMORY;// update the trace timer so the entity will be visible the next 200 time units return qtrue;// and signal the entity is visible } // random tries = (viewer_cl->tracetimer[ent_clnum]+(MEMORY*20) > sv.time)?8:2;// if we have seen an entity recently, we try hard to locate it again for (i=0; i<tries; i++) {// Even if the head is not visible, other body parts might be. Let's check a few randomly selected points end[0] = ent->r.currentOrigin[0] + offsetrandom(ent->r.mins[0], ent->r.maxs[0]); end[1] = ent->r.currentOrigin[1] + offsetrandom(ent->r.mins[1], ent->r.maxs[1]); end[2] = ent->r.currentOrigin[2] + offsetrandom(ent->r.mins[2], ent->r.maxs[2]+3.f); SV_Trace(&tr, start, NULL, NULL, end, viewer_clnum, contents_mask, qfalse); if (tr.fraction == 1 || tr.entityNum==goal_ent) {// if there is a line of sight to the entity // Com_Printf(va("found ent in %i hits\n", i)); viewer_cl->tracetimer[ent_clnum] = sv.time + MEMORY;// update the trace timer so the entity will be visible the next 200 time units VectorSubtract(end, ent->r.currentOrigin, viewer_cl->lasttrace[ent_clnum]);// remember the offset return qtrue;// and signal the entity is visible } } viewer_cl->lasttrace[ent_clnum][0] = offsetrandom(ent->r.mins[0], ent->r.maxs[0]); viewer_cl->lasttrace[ent_clnum][1] = offsetrandom(ent->r.mins[1], ent->r.maxs[1]); viewer_cl->lasttrace[ent_clnum][2] = offsetrandom(ent->r.mins[2], ent->r.maxs[2]+3.f); return (viewer_cl->tracetimer[ent_clnum] > sv.time);// returns true if the entity was visible within the last 200 time units }
/* ================ SV_Status_f ================ */ static void SV_Status_f( void ) { int i, j, l; client_t *cl; playerState_t *ps; const char *s; int ping; // make sure server is running if ( !com_sv_running->integer ) { Com_Printf( "Server is not running.\n" ); return; } Com_Printf ("map: %s\n", sv_mapname->string ); Com_Printf ("cl score ping name address rate \n"); Com_Printf ("-- ----- ---- --------------- --------------------------------------- -----\n"); for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { if (!cl->state) continue; Com_Printf ("%2i ", i); ps = SV_GameClientNum( i ); Com_Printf ("%5i ", ps->persistant[PERS_SCORE]); if (cl->state == CS_CONNECTED) Com_Printf ("CON "); else if (cl->state == CS_ZOMBIE) Com_Printf ("ZMB "); else { ping = cl->ping < 9999 ? cl->ping : 9999; Com_Printf ("%4i ", ping); } Com_Printf ("%s", cl->name); l = 16 - SV_Strlen(cl->name); j = 0; do { Com_Printf (" "); j++; } while(j < l); // TTimo adding a ^7 to reset the color s = NET_AdrToString( cl->netchan.remoteAddress ); Com_Printf ("^7%s", s); l = 39 - strlen(s); j = 0; do { Com_Printf(" "); j++; } while(j < l); Com_Printf (" %5i", cl->rate); Com_Printf ("\n"); } Com_Printf ("\n"); }
/* ================ SV_Status_f ================ */ static void SV_Status_f( void ) { int i, j, l; client_t *cl; playerState_t *ps; const char *s; int ping; float cpu, avg; // make sure server is running if ( !com_sv_running->integer ) { Com_Printf(_( "Server is not running.\n" )); return; } cpu = ( svs.stats.latched_active + svs.stats.latched_idle ); if ( cpu ) { cpu = 100 * svs.stats.latched_active / cpu; } avg = 1000 * svs.stats.latched_active / STATFRAMES; Com_Printf( "cpu utilization : %3i%%\n" "avg response time: %i ms\n" "map: %s\n" "num score ping name lastmsg address qport rate\n" "--- ----- ---- --------------- ------- --------------------- ----- -----\n", ( int ) cpu, ( int ) avg, sv_mapname->string ); for ( i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++ ) { if ( !cl->state ) { continue; } Com_Printf( "%3i ", i ); ps = SV_GameClientNum( i ); Com_Printf( "%5i ", ps->persistant[ PERS_SCORE ] ); if ( cl->state == CS_CONNECTED ) { Com_Printf( "CNCT " ); } else if ( cl->state == CS_ZOMBIE ) { Com_Printf( "ZMBI " ); } else { ping = cl->ping < 9999 ? cl->ping : 9999; Com_Printf( "%4i ", ping ); } Com_Printf( "%s", cl->name ); l = 16 - strlen( cl->name ); for ( j = 0; j < l; j++ ) { Com_Printf( " " ); } Com_Printf( "%7i ", svs.time - cl->lastPacketTime ); s = NET_AdrToString( cl->netchan.remoteAddress ); Com_Printf( "%s", s ); l = 22 - strlen( s ); for ( j = 0; j < l; j++ ) { Com_Printf( " " ); } Com_Printf( "%5i", cl->netchan.qport ); Com_Printf( " %5i", cl->rate ); Com_Printf( "\n" ); } Com_Printf( "\n" ); }
/* ================ SV_Status_f ================ */ static void SV_Status_f( void ) { int i; client_t *cl; playerState_t *ps; const char *s; int ping; char state[32]; qboolean avoidTruncation = qfalse; int j, k; char spaces[32]; char displayName[MAX_NAME_LENGTH]; // make sure server is running if ( !com_sv_running->integer ) { Com_Printf( SP_GetStringText(STR_SERVER_SERVER_NOT_RUNNING) ); return; } if ( Cmd_Argc() > 1 ) { if (!Q_stricmp("notrunc", Cmd_Argv(1))) { avoidTruncation = qtrue; } } Com_Printf ("map: %s\n", sv_mapname->string ); Com_Printf ("num score ping name lastmsg address qport rate\n"); Com_Printf ("--- ----- ---- --------------- ------- --------------------- ----- -----\n"); for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { if (!cl->state) { continue; } if (cl->state == CS_CONNECTED) { strcpy(state, "CNCT "); } else if (cl->state == CS_ZOMBIE) { strcpy(state, "ZMBI "); } else { ping = cl->ping < 9999 ? cl->ping : 9999; sprintf(state, "%4i", ping); } ps = SV_GameClientNum( i ); s = NET_AdrToString( cl->netchan.remoteAddress ); // Count the length of the visible characters in the name and if it's less than 15 fill the rest with spaces k = MV_StrlenSkipColors(cl->name); if ( k < 0 ) k = 0; // Should never happen for( j = 0; j < (15 - k); j++ ) spaces[j] = ' '; spaces[j] = 0; if (!avoidTruncation) MV_CopyStringWithColors( cl->name, displayName, sizeof(displayName), 15 ); // Limit the visible length of the name to 15 characters (not counting colors) else Q_strncpyz( displayName, cl->name, sizeof(displayName) ); Com_Printf ("%3i %5i %s %s^7%s %7i %21s %5i %5i\n", i, ps->persistant[PERS_SCORE], state, displayName, spaces, svs.time - cl->lastPacketTime, s, cl->netchan.qport, cl->rate ); } Com_Printf ("\n"); }
int SV_CanSee(int player, int other) { sharedEntity_t *pent, *oent; playerState_t *ps; vec3_t viewpoint, tmp; int i; // check if bounding box has been changed if (sv_wh_bbox_horz->integer != bbox_horz) { init_horz_delta(); } if (sv_wh_bbox_vert->integer != bbox_vert) { init_vert_delta(); } ps = SV_GameClientNum(player); pent = SV_GentityNum(player); oent = SV_GentityNum(other); // check if 'other' is in the maximum fov allowed if (sv_wh_check_fov->integer > 0) { if (!player_in_fov(pent->s.apos.trBase, pent->s.pos.trBase, oent->s.pos.trBase)) { return 0; } } // check if visible in this frame calc_viewpoint(ps, pent->s.pos.trBase, viewpoint); for (i = 0; i < 8; i++) { VectorCopy(oent->s.pos.trBase, tmp); tmp[0] += delta[i][0]; tmp[1] += delta[i][1]; tmp[2] += delta[i][2] + VOFS; if (is_visible(viewpoint, tmp)) { return 1; } } // predict player positions copy_trajectory(&pent->s.pos, &traject); predict_move(pent, PREDICT_TIME, &traject, pred_ppos); copy_trajectory(&oent->s.pos, &traject); predict_move(oent, PREDICT_TIME, &traject, pred_opos); // Check again if 'other' is in the maximum fov allowed. // FIXME: We use the original viewangle that may have // changed during the move. This could introduce some // errors. if (sv_wh_check_fov->integer > 0) { if (!player_in_fov(pent->s.apos.trBase, pred_ppos, pred_opos)) { return 0; } } // check if expected to be visible in the next frame calc_viewpoint(ps, pred_ppos, viewpoint); for (i = 0; i < 8; i++) { VectorCopy(pred_opos, tmp); tmp[0] += delta[i][0]; tmp[1] += delta[i][1]; tmp[2] += delta[i][2] + VOFS; if (is_visible(viewpoint, tmp)) { return 1; } } return 0; }
/* ============= SV_BuildClientSnapshot Decides which entities are going to be visible to the client, and copies off the playerstate and areabits. This properly handles multiple recursive portals, but the render currently doesn't. For viewing through other player's eyes, clent can be something other than client->gentity ============= */ static void SV_BuildClientSnapshot( client_t *client ) { vec3_t org; clientSnapshot_t *frame; snapshotEntityNumbers_t entityNumbers; int i; sharedEntity_t *ent; entityState_t *state; svEntity_t *svEnt; sharedEntity_t *clent; playerState_t *ps; // bump the counter used to prevent double adding sv.snapshotCounter++; // this is the frame we are creating frame = &client->frames[ client->netchan.outgoingSequence & PACKET_MASK ]; // clear everything in this snapshot entityNumbers.numSnapshotEntities = 0; Com_Memset( frame->areabits, 0, sizeof( frame->areabits ) ); frame->num_entities = 0; clent = client->gentity; if ( !clent || client->state == CS_ZOMBIE ) { return; } // grab the current playerState_t ps = SV_GameClientNum( client - svs.clients ); frame->ps = *ps; #ifdef _ONEBIT_COMBO frame->pDeltaOneBit = &ps->deltaOneBits; frame->pDeltaNumBit = &ps->deltaNumBits; #endif if (ps->m_iVehicleNum) { //get the vehicle's playerstate too then sharedEntity_t *veh = SV_GentityNum(ps->m_iVehicleNum); if (veh && veh->playerState) { //Now VMA it and we've got ourselves a playerState playerState_t *vps = ((playerState_t *)VM_ArgPtr((int)veh->playerState)); frame->vps = *vps; #ifdef _ONEBIT_COMBO frame->pDeltaOneBitVeh = &vps->deltaOneBits; frame->pDeltaNumBitVeh = &vps->deltaNumBits; #endif } } int clientNum; // never send client's own entity, because it can // be regenerated from the playerstate clientNum = frame->ps.clientNum; if ( clientNum < 0 || clientNum >= MAX_GENTITIES ) { Com_Error( ERR_DROP, "SV_SvEntityForGentity: bad gEnt" ); } svEnt = &sv.svEntities[ clientNum ]; svEnt->snapshotCounter = sv.snapshotCounter; // find the client's viewpoint VectorCopy( ps->origin, org ); org[2] += ps->viewheight; // add all the entities directly visible to the eye, which // may include portal entities that merge other viewpoints SV_AddEntitiesVisibleFromPoint( org, frame, &entityNumbers, qfalse ); // if there were portals visible, there may be out of order entities // in the list which will need to be resorted for the delta compression // to work correctly. This also catches the error condition // of an entity being included twice. qsort( entityNumbers.snapshotEntities, entityNumbers.numSnapshotEntities, sizeof( entityNumbers.snapshotEntities[0] ), SV_QsortEntityNumbers ); // now that all viewpoint's areabits have been OR'd together, invert // all of them to make it a mask vector, which is what the renderer wants for ( i = 0 ; i < MAX_MAP_AREA_BYTES/4 ; i++ ) { ((int *)frame->areabits)[i] = ((int *)frame->areabits)[i] ^ -1; } // copy the entity states out frame->num_entities = 0; frame->first_entity = svs.nextSnapshotEntities; for ( i = 0 ; i < entityNumbers.numSnapshotEntities ; i++ ) { ent = SV_GentityNum(entityNumbers.snapshotEntities[i]); state = &svs.snapshotEntities[svs.nextSnapshotEntities % svs.numSnapshotEntities]; *state = ent->s; svs.nextSnapshotEntities++; // this should never hit, map should always be restarted first in SV_Frame if ( svs.nextSnapshotEntities >= 0x7FFFFFFE ) { Com_Error(ERR_FATAL, "svs.nextSnapshotEntities wrapped"); } frame->num_entities++; } }
/* ============= SV_BuildClientSnapshot Decides which entities are going to be visible to the client, and copies off the playerstate and areabits. This properly handles multiple recursive portals, but the render currently doesn't. For viewing through other player's eyes, clent can be something other than client->gentity ============= */ static void SV_BuildClientSnapshot( client_t* client ) { vec3_c org; clientSnapshot_t* frame; snapshotEntityNumbers_t entityNumbers; int i; edict_s* ent; entityState_s* state; svEntity_t* svEnt; edict_s* clent; int clientNum; playerState_s* ps; // bump the counter used to prevent double adding sv.snapshotCounter++; // this is the frame we are creating frame = &client->frames[ client->netchan.outgoingSequence & PACKET_MASK ]; // clear everything in this snapshot entityNumbers.numSnapshotEntities = 0; memset( frame->areabits, 0, sizeof( frame->areabits ) ); // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=62 frame->num_entities = 0; clent = client->gentity; if ( !clent || client->state == CS_ZOMBIE ) { return; } // grab the current playerState_s ps = SV_GameClientNum( client - svs.clients ); frame->ps = *ps; // never send client's own entity, because it can // be regenerated from the playerstate clientNum = frame->ps.clientNum; if ( clientNum < 0 || clientNum >= MAX_GENTITIES ) { Com_Error( ERR_DROP, "SV_SvEntityForGentity: bad gEnt" ); } svEnt = &sv.svEntities[ clientNum ]; svEnt->snapshotCounter = sv.snapshotCounter; // find the client's viewpoint org = ps->origin; org[2] += ps->viewheight; // add all the entities directly visible to the eye, which // may include portal entities that merge other viewpoints bitSet_c areaBits; if ( sv_bsp ) { areaBits.init( sv_bsp->getNumAreas(), false ); } SV_AddEntitiesVisibleFromPoint( org, frame, &entityNumbers, false, areaBits ); memcpy( frame->areabits, areaBits.getArray(), areaBits.getSizeInBytes() ); frame->areabytes = areaBits.getSizeInBytes(); // if there were portals visible, there may be out of order entities // in the list which will need to be resorted for the delta compression // to work correctly. This also catches the error condition // of an entity being included twice. qsort( entityNumbers.snapshotEntities, entityNumbers.numSnapshotEntities, sizeof( entityNumbers.snapshotEntities[0] ), SV_QsortEntityNumbers ); // now that all viewpoint's areabits have been OR'd together, invert // all of them to make it a mask vector, which is what the renderer wants for ( i = 0 ; i < MAX_MAP_AREA_BYTES / 4 ; i++ ) { ( ( int* )frame->areabits )[i] = ( ( int* )frame->areabits )[i] ^ -1; } // copy the entity states out frame->num_entities = 0; frame->first_entity = svs.nextSnapshotEntities; for ( i = 0 ; i < entityNumbers.numSnapshotEntities ; i++ ) { ent = SV_GentityNum( entityNumbers.snapshotEntities[i] ); state = &svs.snapshotEntities[svs.nextSnapshotEntities % svs.numSnapshotEntities]; // copy entityState_s from entity/player *state = *ent->s; svs.nextSnapshotEntities++; // this should never hit, map should always be restarted first in SV_Frame if ( svs.nextSnapshotEntities >= 0x7FFFFFFE ) { Com_Error( ERR_FATAL, "svs.nextSnapshotEntities wrapped" ); } frame->num_entities++; } }
/* ================ SV_Status_f ================ */ static void SV_Status_f( void ) { int i, humans, bots; client_t *cl; playerState_t *ps; const char *s; int ping; char state[32]; qboolean avoidTruncation = qfalse; // make sure server is running if ( !com_sv_running->integer ) { Com_Printf( "Server is not running.\n" ); return; } if ( Cmd_Argc() > 1 ) { if (!Q_stricmp("notrunc", Cmd_Argv(1))) { avoidTruncation = qtrue; } } humans = bots = 0; for ( i = 0 ; i < sv_maxclients->integer ; i++ ) { if ( svs.clients[i].state >= CS_CONNECTED ) { if ( svs.clients[i].netchan.remoteAddress.type != NA_BOT ) { humans++; } else { bots++; } } } #if defined(_WIN32) #define STATUS_OS "Windows" #elif defined(__linux__) #define STATUS_OS "Linux" #elif defined(MACOS_X) #define STATUS_OS "OSX" #else #define STATUS_OS "Unknown" #endif const char *ded_table[] = { "listen", "lan dedicated", "public dedicated", }; char hostname[MAX_HOSTNAMELENGTH] = { 0 }; Q_strncpyz( hostname, sv_hostname->string, sizeof(hostname) ); Q_StripColor( hostname ); Com_Printf( "hostname: %s^7\n", hostname ); Com_Printf( "version : %s %i\n", VERSION_STRING_DOTTED, PROTOCOL_VERSION ); Com_Printf( "game : %s\n", FS_GetCurrentGameDir() ); Com_Printf( "udp/ip : %s:%i os(%s) type(%s)\n", Cvar_VariableString( "net_ip" ), Cvar_VariableIntegerValue( "net_port" ), STATUS_OS, ded_table[com_dedicated->integer] ); Com_Printf( "map : %s gametype(%i)\n", sv_mapname->string, sv_gametype->integer ); Com_Printf( "players : %i humans, %i bots (%i max)\n", humans, bots, sv_maxclients->integer - sv_privateClients->integer ); Com_Printf( "uptime : %s\n", SV_CalcUptime() ); Com_Printf ("cl score ping name address rate \n"); Com_Printf ("-- ----- ---- --------------- --------------------------------------- -----\n"); for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { if ( !cl->state ) continue; if ( cl->state == CS_CONNECTED ) Q_strncpyz( state, "CON ", sizeof( state ) ); else if ( cl->state == CS_ZOMBIE ) Q_strncpyz( state, "ZMB ", sizeof( state ) ); else { ping = cl->ping < 9999 ? cl->ping : 9999; Com_sprintf( state, sizeof(state), "%4i", ping ); } ps = SV_GameClientNum( i ); s = NET_AdrToString( cl->netchan.remoteAddress ); if (!avoidTruncation) { Com_Printf ("%2i %5i %s %-15.15s ^7%39s %5i\n", i, ps->persistant[PERS_SCORE], state, cl->name, s, cl->rate ); } else { Com_Printf ("%2i %5i %s %s ^7%39s %5i\n", i, ps->persistant[PERS_SCORE], state, cl->name, s, cl->rate ); } } Com_Printf ("\n"); }
void SV_LinkEntity( sharedEntity_t *gEnt ) { worldSector_t *node; int leafs[MAX_TOTAL_ENT_LEAFS]; int cluster; int num_leafs; int i; int area; int lastLeaf; float *origin, *angles; svEntity_t *ent; sharedPlayerState_t *ps; ent = SV_SvEntityForGentity( gEnt ); if ( ent->worldSector ) { SV_UnlinkEntity( gEnt ); // unlink from old position } // get the position origin = gEnt->r.currentOrigin; angles = gEnt->r.currentAngles; // set the abs box if ( gEnt->s.bmodel && (angles[0] || angles[1] || angles[2]) ) { // expand for rotation float max; max = RadiusFromBounds( gEnt->s.mins, gEnt->s.maxs ); for (i=0 ; i<3 ; i++) { gEnt->r.absmin[i] = origin[i] - max; gEnt->r.absmax[i] = origin[i] + max; } } else { // normal VectorAdd (origin, gEnt->s.mins, gEnt->r.absmin); VectorAdd (origin, gEnt->s.maxs, gEnt->r.absmax); } // because movement is clipped an epsilon away from an actual edge, // we must fully check even when bounding boxes don't quite touch gEnt->r.absmin[0] -= 1; gEnt->r.absmin[1] -= 1; gEnt->r.absmin[2] -= 1; gEnt->r.absmax[0] += 1; gEnt->r.absmax[1] += 1; gEnt->r.absmax[2] += 1; // link to PVS leafs ent->numClusters = 0; ent->lastCluster = 0; ent->areanum = -1; ent->areanum2 = -1; //get all leafs, including solids num_leafs = CM_BoxLeafnums( gEnt->r.absmin, gEnt->r.absmax, leafs, MAX_TOTAL_ENT_LEAFS, &lastLeaf ); // if none of the leafs were inside the map, the // entity is outside the world and can be considered unlinked if ( !num_leafs ) { return; } // set areas, even from clusters that don't fit in the entity array for (i=0 ; i<num_leafs ; i++) { area = CM_LeafArea (leafs[i]); if (area != -1) { // doors may legally straggle two areas, // but nothing should evern need more than that if (ent->areanum != -1 && ent->areanum != area) { if (ent->areanum2 != -1 && ent->areanum2 != area && sv.state == SS_LOADING) { Com_DPrintf ("Object %i touching 3 areas at %f %f %f\n", gEnt->s.number, gEnt->r.absmin[0], gEnt->r.absmin[1], gEnt->r.absmin[2]); } ent->areanum2 = area; } else { ent->areanum = area; } } } // store as many explicit clusters as we can ent->numClusters = 0; for (i=0 ; i < num_leafs ; i++) { cluster = CM_LeafCluster( leafs[i] ); if ( cluster != -1 ) { ent->clusternums[ent->numClusters++] = cluster; if ( ent->numClusters == MAX_ENT_CLUSTERS ) { break; } } } // store off a last cluster if we need to if ( i != num_leafs ) { ent->lastCluster = CM_LeafCluster( lastLeaf ); } gEnt->r.linkcount++; // find the first world sector node that the ent's box crosses node = sv_worldSectors; while (1) { if (node->axis == -1) break; if ( gEnt->r.absmin[node->axis] > node->dist) node = node->children[0]; else if ( gEnt->r.absmax[node->axis] < node->dist) node = node->children[1]; else break; // crosses the node } // link it in ent->worldSector = node; ent->nextEntityInWorldSector = node->entities; node->entities = ent; gEnt->r.linked = qtrue; if (gEnt->s.number < MAX_CLIENTS) { ps = SV_GameClientNum(gEnt->s.number); ps->linked = qtrue; } }
/* ================ SV_Status_f ================ */ static void SV_Status_f(void) { int i, j, l; client_t *cl; playerState_t *ps; const char *s; int ping; // make sure server is running if (!com_sv_running->integer) { Com_Printf("Server is not running.\n"); return; } Com_Printf("cpu server utilization: %i %%\n" "avg response time: %i ms\n" "map: %s\n" "num score ping name lastmsg address qport rate\n" "--- ----- ---- --------------- ------- --------------------- ----- -----\n", ( int ) svs.stats.cpu, ( int ) svs.stats.avg, sv_mapname->string); // FIXME: extend player name lenght (>16 chars) ? - they are printed! // FIXME: do a Com_Printf per line! ... create the row at first for (i = 0, cl = svs.clients ; i < sv_maxclients->integer ; i++, cl++) { if (!cl->state) { continue; } Com_Printf("%3i ", i); ps = SV_GameClientNum(i); Com_Printf("%5i ", ps->persistant[PERS_SCORE]); if (cl->state == CS_CONNECTED) { Com_Printf("CNCT "); } else if (cl->state == CS_ZOMBIE) { Com_Printf("ZMBI "); } else { ping = cl->ping < 9999 ? cl->ping : 9999; Com_Printf("%4i ", ping); } Com_Printf("%s", rc(cl->name)); l = 16 - strlen(cl->name); for (j = 0 ; j < l ; j++) { Com_Printf(" "); } Com_Printf("%7i ", svs.time - cl->lastPacketTime); s = NET_AdrToString(cl->netchan.remoteAddress); Com_Printf("%s", s); l = 22 - strlen(s); for (j = 0 ; j < l ; j++) { Com_Printf(" "); } Com_Printf("%5i", cl->netchan.qport); Com_Printf(" %5i\n", cl->rate); } Com_Printf("\n"); }
/* ================ SV_Status_f ================ */ static void SV_Status_f( void ) { int i; client_t *cl; playerState_t *ps; const char *s; int ping; char state[32]; qboolean avoidTruncation = qfalse; // make sure server is running if ( !com_sv_running->integer ) { Com_Printf( SE_GetString("STR_SERVER_SERVER_NOT_RUNNING") ); return; } if ( Cmd_Argc() > 1 ) { if (!Q_stricmp("notrunc", Cmd_Argv(1))) { avoidTruncation = qtrue; } } Com_Printf ("map: %s\n", sv_mapname->string ); Com_Printf ("num score ping name lastmsg address qport rate\n"); Com_Printf ("--- ----- ---- --------------- ------- --------------------- ----- -----\n"); for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++) { if (!cl->state) { continue; } if (cl->state == CS_CONNECTED) { strcpy(state, "CNCT "); } else if (cl->state == CS_ZOMBIE) { strcpy(state, "ZMBI "); } else { ping = cl->ping < 9999 ? cl->ping : 9999; sprintf(state, "%4i", ping); } ps = SV_GameClientNum( i ); s = NET_AdrToString( cl->netchan.remoteAddress ); if (!avoidTruncation) { Com_Printf ("%3i %5i %s %-15.15s %7i %21s %5i %5i\n", i, ps->persistant[PERS_SCORE], state, cl->name, svs.time - cl->lastPacketTime, s, cl->netchan.qport, cl->rate ); } else { Com_Printf ("%3i %5i %s %s %7i %21s %5i %5i\n", i, ps->persistant[PERS_SCORE], state, cl->name, svs.time - cl->lastPacketTime, s, cl->netchan.qport, cl->rate ); } } Com_Printf ("\n"); }