From a85a677ab302b0dec0cb716e5c071305825df666 Mon Sep 17 00:00:00 2001 From: Christopher Schwarz Date: Wed, 14 Oct 2009 18:57:42 +0000 Subject: * (bug 2991) Add teamoverlay, an optional hud display showing names, health, class/equipment, and locations of teammates - Disable with cg_drawTeamOverlay 0 or remove it from your hud - Control number of teammates to show with cg_teamOverlayMaxPlayers - When teamoverlay is enabled, show health of teammates next to their name when the crosshair is over them - Servers who dislike gameplay effects or want to save on bandwidth use can use g_allowTeamOverlay 0 to disable the feature for players - Special thanks to Risujin for bring this idea and the original patch to Tremulous * Cache teaminfo on the server and send only when needed (Undeference) * Increase frequency of sending updated teaminfo (Undeference) --- src/cgame/cg_draw.c | 139 +++++++++++++++++++++++++++++++++++++++++++--- src/cgame/cg_drawtools.c | 27 +++++++++ src/cgame/cg_local.h | 16 +++--- src/cgame/cg_main.c | 9 ++- src/cgame/cg_servercmds.c | 19 +++---- src/game/bg_public.h | 5 +- src/game/g_local.h | 3 + src/game/g_main.c | 2 + src/game/g_team.c | 133 +++++++++++++++++++++++++++----------------- 9 files changed, 270 insertions(+), 83 deletions(-) (limited to 'src') diff --git a/src/cgame/cg_draw.c b/src/cgame/cg_draw.c index 18427b8a..2b74a3f9 100644 --- a/src/cgame/cg_draw.c +++ b/src/cgame/cg_draw.c @@ -30,11 +30,6 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA menuDef_t *menuScoreboard = NULL; -int drawTeamOverlayModificationCount = -1; - -int sortedTeamPlayers[ TEAM_MAXOVERLAY ]; -int numSortedTeamPlayers; - static void CG_AlignText( rectDef_t *rect, const char *text, float scale, float w, float h, int align, int valign, @@ -1620,6 +1615,123 @@ static void CG_DrawTimer( rectDef_t *rect, float text_x, float text_y, } } +/* +================= +CG_DrawTeamOverlay +================= +*/ + +static void CG_DrawTeamOverlay( rectDef_t *rect, float scale, vec4_t color ) +{ + char *s; + int i; + float x = rect->x; + float y, dx; + clientInfo_t *ci, *pci; + vec4_t tcolor; + float iconSize = rect->h/8.0f; + float leftMargin = 4.0f; + float iconTopMargin = 2.0f; + float midSep = 2.0f; + float backgroundWidth = rect->w; + float fontScale = 0.30f; + int maxDisplayCount = 0; + int displayCount = 0; + weapon_t curWeapon = WP_NONE; + + if( cg.predictedPlayerState.pm_type == PM_SPECTATOR ) + return; + + if( !cg_drawTeamOverlay.integer || !cg_teamOverlayMaxPlayers.integer ) + return; + + if( !cgs.teaminfoReceievedTime ) return; + + pci = cgs.clientinfo + cg.snap->ps.clientNum; + + + for( i = 0; i < MAX_CLIENTS; i++ ) + { + ci = cgs.clientinfo + i; + if( ci->infoValid && pci != ci && ci->team == pci->team) + maxDisplayCount++; + } + + if( maxDisplayCount > cg_teamOverlayMaxPlayers.integer ) + maxDisplayCount = cg_teamOverlayMaxPlayers.integer; + + iconSize *= scale; + leftMargin *= scale; + iconTopMargin *= scale; + midSep *= scale; + backgroundWidth *= scale; + fontScale *= scale; + + y = rect->y - (float) maxDisplayCount * ( iconSize / 2.0f ); + + tcolor[ 0 ] = 1.0f; + tcolor[ 1 ] = 1.0f; + tcolor[ 2 ] = 1.0f; + tcolor[ 3 ] = color[ 3 ]; + + for( i = 0; i < MAX_CLIENTS && displayCount < maxDisplayCount; i++ ) + { + ci = cgs.clientinfo + i; + + if( !ci->infoValid || pci == ci || ci->team != pci->team ) + continue; + + dx = 0.f; + trap_R_SetColor( color ); + CG_DrawPic( x, y-iconSize+iconTopMargin, backgroundWidth, + iconSize, cgs.media.teamOverlayShader ); + trap_R_SetColor( tcolor ); + if( ci->health <= 0 || !ci->curWeaponClass ) + { + dx = -iconSize; + s = va( "%s^7", ci->name ); + } + else + { + if( ci->team == TEAM_HUMANS ) + curWeapon = ci->curWeaponClass; + else if( ci->team == TEAM_ALIENS ) + curWeapon = BG_Class( ci->curWeaponClass )->startWeapon; + + CG_DrawPic( x+leftMargin, y-iconSize+iconTopMargin, iconSize, iconSize, + cg_weapons[ curWeapon ].weaponIcon ); + if( cg.predictedPlayerState.stats[ STAT_TEAM ] == TEAM_HUMANS ) + { + if( ci->upgrade != UP_NONE ) + { + CG_DrawPic( x+iconSize+leftMargin, y-iconSize+iconTopMargin, + iconSize, iconSize, cg_upgrades[ ci->upgrade ].upgradeIcon ); + dx = iconSize; + } + } + else + { + if( curWeapon == WP_ABUILD2 || curWeapon == WP_ALEVEL1_UPG || + curWeapon == WP_ALEVEL2_UPG || curWeapon == WP_ALEVEL3_UPG ) + { + CG_DrawPic( x+iconSize+leftMargin, y-iconSize+iconTopMargin, + iconSize, iconSize, cgs.media.upgradeClassIconShader ); + dx = iconSize; + } + } + s = va( "%s^7 [^%c%d^7] ^7%s", ci->name, + CG_GetColorCharForHealth( i ), + ci->health, + CG_ConfigString( CS_LOCATIONS + ci->location ) ); + } + trap_R_SetColor( NULL ); + UI_Text_Paint( x+iconSize+leftMargin+midSep+dx, y, fontScale, tcolor, s, + 0, 0, ITEM_TEXTSTYLE_NORMAL ); + y += iconSize; + displayCount++; + } +} + /* ================= CG_DrawClock @@ -2244,10 +2356,20 @@ static void CG_DrawCrosshairNames( rectDef_t *rect, float scale, int textStyle ) return; } + // add health from overlay info to the crosshair client name name = cgs.clientinfo[ cg.crosshairClientNum ].name; + if( cg_teamOverlayUserinfo.integer && + cg.snap->ps.stats[ STAT_TEAM ] != TEAM_NONE && + cgs.teaminfoReceievedTime ) + { + name = va( "%s ^7[^%c%d^7]", name, + CG_GetColorCharForHealth( cg.crosshairClientNum ), + cgs.clientinfo[ cg.crosshairClientNum ].health ); + } + w = UI_Text_Width( name, scale, 0 ); - x = rect->x + rect->w / 2; - UI_Text_Paint( x - w / 2, rect->y + rect->h, scale, color, name, 0, 0, textStyle ); + x = rect->x + rect->w / 2.0f; + UI_Text_Paint( x - w / 2.0f, rect->y + rect->h, scale, color, name, 0, 0, textStyle ); trap_R_SetColor( NULL ); } @@ -2440,6 +2562,9 @@ void CG_OwnerDraw( float x, float y, float w, float h, float text_x, case CG_LAGOMETER: CG_DrawLagometer( &rect, text_x, text_y, scale, foreColor ); break; + case CG_TEAMOVERLAY: + CG_DrawTeamOverlay( &rect, scale, foreColor ); + break; case CG_DEMO_PLAYBACK: CG_DrawDemoPlayback( &rect, foreColor, shader ); diff --git a/src/cgame/cg_drawtools.c b/src/cgame/cg_drawtools.c index 7d32fa1d..17eb7ac6 100644 --- a/src/cgame/cg_drawtools.c +++ b/src/cgame/cg_drawtools.c @@ -403,3 +403,30 @@ char *CG_KeyBinding( const char *bind ) } return key; } + +/* +================= +CG_GetColorCharForHealth +================= +*/ +char CG_GetColorCharForHealth( int clientnum ) +{ + char health_char = '2'; + int healthPercent; + int maxHealth; + int curWeaponClass = cgs.clientinfo[ clientnum ].curWeaponClass; + + if( cgs.clientinfo[ clientnum ].team == TEAM_ALIENS ) + maxHealth = BG_Class( curWeaponClass )->health; + else + maxHealth = BG_Class( PCL_HUMAN )->health; + + healthPercent = (int) ( 100.0f * (float) cgs.clientinfo[ clientnum ].health + / (float) maxHealth ); + + if( healthPercent < 33 ) + health_char = '1'; + else if( healthPercent < 67 ) + health_char = '3'; + return health_char; +} diff --git a/src/cgame/cg_local.h b/src/cgame/cg_local.h index d14454a1..7302ac78 100644 --- a/src/cgame/cg_local.h +++ b/src/cgame/cg_local.h @@ -730,8 +730,8 @@ typedef struct int score; // updated by score servercmds int location; // location index for team mode int health; // you only get this info about your teammates - int armor; - int curWeapon; + int upgrade; + int curWeaponClass; // sends current weapon for H, current class for A int handicap; @@ -1188,6 +1188,7 @@ typedef struct qhandle_t scannerBlipShader; qhandle_t scannerLineShader; + qhandle_t teamOverlayShader; qhandle_t numberShaders[ 11 ]; @@ -1384,6 +1385,8 @@ typedef struct clientInfo_t clientinfo[ MAX_CLIENTS ]; + int teaminfoReceievedTime; + // corpse info clientInfo_t corpseinfo[ MAX_CLIENTS ]; @@ -1440,6 +1443,9 @@ extern vmCvar_t cg_drawChargeBar; extern vmCvar_t cg_drawCrosshair; extern vmCvar_t cg_drawCrosshairNames; extern vmCvar_t cg_crosshairSize; +extern vmCvar_t cg_drawTeamOverlay; +extern vmCvar_t cg_teamOverlayMaxPlayers; +extern vmCvar_t cg_teamOverlayUserinfo; extern vmCvar_t cg_draw2D; extern vmCvar_t cg_animSpeed; extern vmCvar_t cg_debugAnim; @@ -1596,20 +1602,16 @@ int CG_DrawStrlen( const char *str ); float *CG_FadeColor( int startMsec, int totalMsec ); void CG_TileClear( void ); void CG_ColorForHealth( vec4_t hcolor ); -void CG_GetColorForHealth( int health, int armor, vec4_t hcolor ); - void CG_DrawRect( float x, float y, float width, float height, float size, const float *color ); void CG_DrawSides(float x, float y, float w, float h, float size); void CG_DrawTopBottom(float x, float y, float w, float h, float size); qboolean CG_WorldToScreen( vec3_t point, float *x, float *y ); char *CG_KeyBinding( const char *bind ); - +char CG_GetColorCharForHealth( int clientnum ); // // cg_draw.c // -extern int sortedTeamPlayers[ TEAM_MAXOVERLAY ]; -extern int numSortedTeamPlayers; void CG_AddLagometerFrameInfo( void ); void CG_AddLagometerSnapshotInfo( snapshot_t *snap ); diff --git a/src/cgame/cg_main.c b/src/cgame/cg_main.c index 89082cee..bd3acec5 100644 --- a/src/cgame/cg_main.c +++ b/src/cgame/cg_main.c @@ -153,8 +153,10 @@ vmCvar_t cg_synchronousClients; vmCvar_t cg_stats; vmCvar_t cg_paused; vmCvar_t cg_blood; -vmCvar_t cg_teamOverlayUserinfo; vmCvar_t cg_teamChatsOnly; +vmCvar_t cg_drawTeamOverlay; +vmCvar_t cg_teamOverlayMaxPlayers; +vmCvar_t cg_teamOverlayUserinfo; vmCvar_t cg_noPrintDuplicate; vmCvar_t cg_noVoiceChats; vmCvar_t cg_noVoiceText; @@ -270,6 +272,8 @@ static cvarTable_t cvarTable[ ] = { &cg_thirdPersonPitchFollow, "cg_thirdPersonPitchFollow", "0", 0 }, { &cg_thirdPersonShoulderViewMode, "cg_thirdPersonShoulderViewMode", "1", CVAR_ARCHIVE }, { &cg_stats, "cg_stats", "0", 0 }, + { &cg_drawTeamOverlay, "cg_drawTeamOverlay", "1", CVAR_ARCHIVE }, + { &cg_teamOverlayMaxPlayers, "cg_teamOverlayMaxPlayers", "8", CVAR_ARCHIVE }, { &cg_teamOverlayUserinfo, "teamoverlay", "1", CVAR_ARCHIVE|CVAR_USERINFO }, { &cg_teamChatsOnly, "cg_teamChatsOnly", "0", CVAR_ARCHIVE }, { &cg_noPrintDuplicate, "cg_noPrintDuplicate", "0", CVAR_ARCHIVE }, @@ -428,6 +432,7 @@ void CG_UpdateCvars( void ) // check for modications here CG_SetUIVars( ); + } @@ -747,6 +752,8 @@ static void CG_RegisterGraphics( void ) cgs.media.scannerBlipShader = trap_R_RegisterShader( "gfx/2d/blip" ); cgs.media.scannerLineShader = trap_R_RegisterShader( "gfx/2d/stalk" ); + cgs.media.teamOverlayShader = trap_R_RegisterShader( "gfx/2d/teamoverlay" ); + cgs.media.tracerShader = trap_R_RegisterShader( "gfx/misc/tracer" ); cgs.media.backTileShader = trap_R_RegisterShader( "console" ); diff --git a/src/cgame/cg_servercmds.c b/src/cgame/cg_servercmds.c index 4c462fe6..cd590c40 100644 --- a/src/cgame/cg_servercmds.c +++ b/src/cgame/cg_servercmds.c @@ -79,17 +79,14 @@ CG_ParseTeamInfo static void CG_ParseTeamInfo( void ) { int i; + int count; int client; - numSortedTeamPlayers = atoi( CG_Argv( 1 ) ); - if( numSortedTeamPlayers < 0 || numSortedTeamPlayers > TEAM_MAXOVERLAY ) - { - CG_Error( "CG_ParseTeamInfo: numSortedTeamPlayers out of range (%d)", - numSortedTeamPlayers ); - return; - } + count = atoi( CG_Argv( 1 ) ); - for( i = 0; i < numSortedTeamPlayers; i++ ) + cgs.teaminfoReceievedTime = cg.time; + + for( i = 0; i < count; i++ ) { client = atoi( CG_Argv( i * 5 + 2 ) ); if( client < 0 || client >= MAX_CLIENTS ) @@ -98,12 +95,10 @@ static void CG_ParseTeamInfo( void ) return; } - sortedTeamPlayers[ i ] = client; - cgs.clientinfo[ client ].location = atoi( CG_Argv( i * 5 + 3 ) ); cgs.clientinfo[ client ].health = atoi( CG_Argv( i * 5 + 4 ) ); - cgs.clientinfo[ client ].armor = atoi( CG_Argv( i * 5 + 5 ) ); - cgs.clientinfo[ client ].curWeapon = atoi( CG_Argv( i * 5 + 6 ) ); + cgs.clientinfo[ client ].curWeaponClass = atoi( CG_Argv( i * 5 + 5 ) ); + cgs.clientinfo[ client ].upgrade = atoi( CG_Argv( i * 5 + 6 ) ); } } diff --git a/src/game/bg_public.h b/src/game/bg_public.h index 0d9a4838..d1cc4572 100644 --- a/src/game/bg_public.h +++ b/src/game/bg_public.h @@ -789,10 +789,7 @@ typedef struct animation_s #define ANIM_FORCEBIT 0x40 // Time between location updates -#define TEAM_LOCATION_UPDATE_TIME 1000 - -// How many players on the overlay -#define TEAM_MAXOVERLAY 32 +#define TEAM_LOCATION_UPDATE_TIME 500 // player classes typedef enum diff --git a/src/game/g_local.h b/src/game/g_local.h index c9d06285..f8c141ec 100644 --- a/src/game/g_local.h +++ b/src/game/g_local.h @@ -339,6 +339,8 @@ typedef struct int adminLevel; char voice[ MAX_VOICE_NAME_LEN ]; qboolean useUnlagged; + // keep track of other players' info for tinfo + char cinfo[ MAX_CLIENTS ][ 16 ]; } clientPersistant_t; #define MAX_UNLAGGED_MARKERS 10 @@ -1133,6 +1135,7 @@ extern vmCvar_t g_dretchPunt; extern vmCvar_t g_privateMessages; extern vmCvar_t g_specChat; extern vmCvar_t g_publicAdminMessages; +extern vmCvar_t g_allowTeamOverlay; void trap_Print( const char *fmt ); void trap_Error( const char *fmt ); diff --git a/src/game/g_main.c b/src/game/g_main.c index 1386afa2..7f104a15 100644 --- a/src/game/g_main.c +++ b/src/game/g_main.c @@ -138,6 +138,7 @@ vmCvar_t g_dretchPunt; vmCvar_t g_privateMessages; vmCvar_t g_specChat; vmCvar_t g_publicAdminMessages; +vmCvar_t g_allowTeamOverlay; vmCvar_t g_tag; @@ -264,6 +265,7 @@ static cvarTable_t gameCvarTable[ ] = { &g_privateMessages, "g_privateMessages", "1", CVAR_ARCHIVE, 0, qfalse }, { &g_specChat, "g_specChat", "1", CVAR_ARCHIVE, 0, qfalse }, { &g_publicAdminMessages, "g_publicAdminMessages", "1", CVAR_ARCHIVE, 0, qfalse }, + { &g_allowTeamOverlay, "g_allowTeamOverlay", "1", CVAR_ARCHIVE, 0, qtrue }, { &g_tag, "g_tag", "main", CVAR_INIT, 0, qfalse } }; diff --git a/src/game/g_team.c b/src/game/g_team.c index db55c625..dbaf2b40 100644 --- a/src/game/g_team.c +++ b/src/game/g_team.c @@ -240,6 +240,7 @@ void G_ChangeTeam( gentity_t *ent, team_t newTeam ) ent->s.number, newTeam, oldTeam, ent->client->pers.netname, BG_TeamName( newTeam ) ); } + TeamplayInfoMessage( ent ); } /* @@ -277,83 +278,112 @@ gentity_t *Team_GetLocation( gentity_t *ent ) /*---------------------------------------------------------------------------*/ -static int QDECL SortClients( const void *a, const void *b ) -{ - return *(int *)a - *(int *)b; -} - - /* ================== -TeamplayLocationsMessage +TeamplayInfoMessage Format: - clientNum location health armor weapon misc + clientNum location health weapon upgrade ================== */ void TeamplayInfoMessage( gentity_t *ent ) { - char entry[ 1024 ]; - char string[ 8192 ]; - int stringlength; - int i, j; + char entry[ 19 ], string[ 1143 ]; + int i, j, team, stringlength; + int sent = 0; gentity_t *player; - int cnt; - int h, a = 0; - int clients[ TEAM_MAXOVERLAY ]; + gclient_t *cl; + upgrade_t upgrade = UP_NONE; + int curWeaponClass = WP_NONE ; // sends weapon for humans, class for aliens + char *tmp; - if( ! ent->client->pers.teamInfo ) - return; + if( !g_allowTeamOverlay.integer ) + return; - // figure out what client should be on the display - // we are limited to 8, but we want to use the top eight players - // but in client order (so they don't keep changing position on the overlay) - for( i = 0, cnt = 0; i < g_maxclients.integer && cnt < TEAM_MAXOVERLAY; i++ ) - { - player = g_entities + level.sortedClients[ i ]; + if( !ent->client->pers.teamInfo ) + return; - if( player->inuse && player->client->ps.stats[ STAT_TEAM ] == - ent->client->ps.stats[ STAT_TEAM ] ) - clients[ cnt++ ] = level.sortedClients[ i ]; + if( ent->client->pers.teamSelection == TEAM_NONE ) + { + if( ent->client->sess.spectatorState == SPECTATOR_FREE || + ent->client->sess.spectatorClient < 0 ) + return; + team = g_entities[ ent->client->sess.spectatorClient ].client-> + pers.teamSelection; } + else + team = ent->client->pers.teamSelection; - // We have the top eight players, sort them by clientNum - qsort( clients, cnt, sizeof( clients[ 0 ] ), SortClients ); - - // send the latest information on all clients - string[ 0 ] = 0; + string[ 0 ] = '\0'; stringlength = 0; - for( i = 0, cnt = 0; i < g_maxclients.integer && cnt < TEAM_MAXOVERLAY; i++) + for( i = 0; i < MAX_CLIENTS; i++) { - player = g_entities + i; + player = g_entities + i ; + cl = player->client; + + if( ent == player || !cl || team != cl->pers.teamSelection || + !player->inuse ) + continue; - if( player->inuse && player->client->ps.stats[ STAT_TEAM ] == - ent->client->ps.stats[ STAT_TEAM ] ) + if( cl->sess.spectatorState != SPECTATOR_NOT ) { - h = player->client->ps.stats[ STAT_HEALTH ]; + curWeaponClass = WP_NONE; + upgrade = UP_NONE; + } + else if (cl->pers.teamSelection == TEAM_HUMANS ) + { + curWeaponClass = cl->ps.weapon; + + if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, cl->ps.stats ) ) + upgrade = UP_BATTLESUIT; + else if( BG_InventoryContainsUpgrade( UP_JETPACK, cl->ps.stats ) ) + upgrade = UP_JETPACK; + else if( BG_InventoryContainsUpgrade( UP_BATTPACK, cl->ps.stats ) ) + upgrade = UP_BATTPACK; + else if( BG_InventoryContainsUpgrade( UP_HELMET, cl->ps.stats ) ) + upgrade = UP_HELMET; + else if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, cl->ps.stats ) ) + upgrade = UP_LIGHTARMOUR; + else + upgrade = UP_NONE; + } + else if( cl->pers.teamSelection == TEAM_ALIENS ) + { + curWeaponClass = cl->ps.stats[ STAT_CLASS ]; + upgrade = UP_NONE; + } + + tmp = va( "%i %i %i %i", + player->client->pers.location, + player->client->ps.stats[ STAT_HEALTH ] < 1 ? 0 : + player->client->ps.stats[ STAT_HEALTH ], + curWeaponClass, + upgrade ); + + if( !strcmp( ent->client->pers.cinfo[ i ], tmp ) ) + continue; - if( h < 0 ) - h = 0; + Q_strncpyz( ent->client->pers.cinfo[ i ], tmp, + sizeof( ent->client->pers.cinfo[ i ] ) ); - Com_sprintf( entry, sizeof( entry ), - " %i %i %i %i %i", - i, player->client->pers.location, h, a, - player->client->ps.weapon ); + Com_sprintf( entry, sizeof( entry ), " %i %s", i, tmp ); - j = strlen( entry ); + j = strlen( entry ); - if( stringlength + j > sizeof( string ) ) - break; + if( stringlength + j > sizeof( string ) ) + break; - strcpy( string + stringlength, entry ); - stringlength += j; - cnt++; - } + strcpy( string + stringlength, entry ); + stringlength += j; + sent++; } - trap_SendServerCommand( ent - g_entities, va( "tinfo %i%s", cnt, string ) ); + if( !sent ) + return; + + trap_SendServerCommand( ent - g_entities, va( "tinfo %i%s", sent, string ) ); } void CheckTeamStatus( void ) @@ -390,8 +420,7 @@ void CheckTeamStatus( void ) if( ent->client->pers.connected != CON_CONNECTED ) continue; - if( ent->inuse && ( ent->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS || - ent->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) ) + if( ent->inuse ) TeamplayInfoMessage( ent ); } } -- cgit