/* * SCR_GetNextColumnLayout */ static const char *SCR_GetNextColumnLayout( const char **ptrlay, const char **ptrtitle, char *type, int *width, struct qfontface_s *font ) { static const char *empty = ""; const char *token; assert( ptrlay && *ptrlay ); // get the token type from the layout token = COM_ParseExt( ptrlay, true ); if( !token[0] ) return NULL; if( token[0] != '%' ) CG_Error( "SCR_GetNextColumnLayout: Invalid player tab layout (expecting token type. found '%s')\n", token ); if( type ) *type = token[1]; // get the column width from the layout token = COM_ParseExt( ptrlay, true ); if( !token[0] || token[0] == '%' ) CG_Error( "SCR_GetNextColumnLayout: Invalid player tab layout (expecting token width. found '%s')\n", token ); if( width ) { float widthScale = cg_scoreboardWidthScale->value; bool relative = true; if( token[0] == 'l' ) // line heights { widthScale *= trap_SCR_FontHeight( font ); relative = false; token++; } *width = (int)( atof( token ) * widthScale ); if( relative ) *width = *width * cgs.vidHeight / 600; if( *width < 0 ) *width = 0; } if( ptrtitle && *ptrtitle ) { // get the column title token from the layout token = COM_ParseExt( ptrtitle, true ); if( !token[0] ) CG_Error( "SCR_GetNextColumnLayout: Invalid player tab layout (expecting token tittle. found '%s')\n", token ); } else { token = empty; } return token; }
/* * SCR_DrawChallengers */ static int SCR_DrawChallengers( const char **ptrptr, int x, int y, int panelWidth, struct qfontface_s *font, int pass ) { const char *token; char string[MAX_STRING_CHARS]; int yoffset = 0, xoffset = 0; int playerNum, ping; int height; assert( ptrptr && *ptrptr ); height = trap_SCR_FontHeight( font ); // draw title yoffset = height; if( pass ) { trap_SCR_DrawString( x + xoffset, y + yoffset, ALIGN_CENTER_TOP, CG_TranslateString( "Challengers" ), font, colorCyan ); } yoffset += height; // draw challengers while( *ptrptr ) { if( !SCR_ParseToken( ptrptr, &token ) ) break; // first token is played id playerNum = atoi( token ); if( playerNum < 0 || playerNum >= gs.maxclients ) break; // get a second token if( !SCR_ParseToken( ptrptr, &token ) ) break; // second token is ping ping = atoi( token ); // draw the challenger if( ping < 0 ) Q_snprintfz( string, sizeof( string ), "%s%s ...", cgs.clientInfo[playerNum].name, S_COLOR_WHITE ); else Q_snprintfz( string, sizeof( string ), "%s%s %i", cgs.clientInfo[playerNum].name, S_COLOR_WHITE, ping ); if( pass ) { trap_SCR_DrawString( x + xoffset, y + yoffset, ALIGN_CENTER_TOP, string, font, colorWhite ); } yoffset += height; } yoffset += height; return yoffset; }
/* * SCR_DrawPlayerIcons */ static void SCR_DrawPlayerIcons( struct qfontface_s *font ) { if( !scr_numplayericons ) return; qsort( scr_playericons, scr_numplayericons, sizeof( scr_playericons[0] ), ( int (*)( const void *, const void * ) )SCR_ComparePlayerIcons ); int height = trap_SCR_FontHeight( font ); vec4_t color; Vector4Copy( colorWhite, color ); for( unsigned i = 0; i < scr_numplayericons; i++ ) { scr_playericon_t &icon = scr_playericons[i]; color[3] = icon.alpha; trap_R_DrawStretchPic( icon.x, icon.y, height, height, 0, 0, 1, 1, color, icon.image ); } scr_numplayericons = 0; }
/* * CG_DrawScoreboard */ void CG_DrawScoreboard( void ) { int pass; const char *ptr, *token, *layout; char title[MAX_CONFIGSTRING_CHARS], type; int team = TEAM_PLAYERS; int xpos; int ypos, yoffset, maxyoffset; struct qfontface_s *font; struct qfontface_s *monofont; struct qfontface_s *titlefont; int width, panelWidth; vec4_t whiteTransparent = { 1.0f, 1.0f, 1.0f, 0.5f }; // no layout defined if( !cgs.configStrings[CS_SCB_PLAYERTAB_LAYOUT][0] ) return; if( scoreboardString[0] != '&' ) // nothing to draw return; font = CG_ScoreboardFont( cg_scoreboardFontFamily, cg_scoreboardFontSize ); monofont = CG_ScoreboardFont( cg_scoreboardMonoFontFamily, cg_scoreboardFontSize ); titlefont = CG_ScoreboardFont( cg_scoreboardTitleFontFamily, cg_scoreboardTitleFontSize ); xpos = (int)( cgs.vidWidth * 0.5 ); ypos = (int)( cgs.vidHeight * 0.2 ) - 24 * cgs.vidHeight / 600; // draw title Q_strncpyz( title, cgs.configStrings[CS_GAMETYPETITLE], sizeof( title ) ); if( !title[0] ) Q_strncpyz( title, gs.gametypeName, sizeof( title ) ); Q_strupr( title ); trap_SCR_DrawString( xpos, ypos, ALIGN_CENTER_TOP, title, titlefont, whiteTransparent ); ypos += trap_SCR_FontHeight( titlefont ); trap_SCR_DrawStringWidth( xpos, ypos, ALIGN_CENTER_TOP, cgs.configStrings[CS_HOSTNAME], cgs.vidWidth*0.75, font, whiteTransparent ); ypos += trap_SCR_FontHeight( font ); // calculate the panel width from the layout panelWidth = 0; layout = cgs.configStrings[CS_SCB_PLAYERTAB_LAYOUT]; while( SCR_GetNextColumnLayout( &layout, NULL, &type, &width, font ) != NULL ) { if( !SCR_SkipColumn( type ) ) panelWidth += width; } // parse and draw the scoreboard message for ( pass = 0; pass < 2; pass++ ) { yoffset = 0; maxyoffset = 0; scr_numplayericons = 0; ptr = scoreboardString; while ( ptr ) { token = COM_ParseExt( &ptr, true ); if ( token[0] != '&' ) break; if ( !Q_stricmp( token, "&t" ) ) // team tab { yoffset = 0; yoffset += SCR_DrawTeamTab( &ptr, &team, xpos, ypos + yoffset, panelWidth, font, titlefont, pass ); } else if ( !Q_stricmp( token, "&p" ) ) // player tab { yoffset += SCR_DrawPlayerTab( &ptr, team, xpos, ypos + yoffset, panelWidth, font, pass ); } else if ( !Q_stricmp( token, "&w" ) ) // list of challengers { if ( yoffset < maxyoffset ) yoffset = maxyoffset; maxyoffset += SCR_DrawChallengers( &ptr, xpos, ypos + yoffset, panelWidth, font, pass ); } else if ( !Q_stricmp( token, "&s" ) ) // list of spectators { if ( yoffset < maxyoffset ) yoffset = maxyoffset; maxyoffset += SCR_DrawSpectators( &ptr, xpos, ypos + yoffset, panelWidth, font, true, "Spectators", colorYellow, pass ); } else if( !Q_stricmp( token, "&y" ) ) // list of chasers { if( yoffset < maxyoffset ) yoffset = maxyoffset; if( cg_showChasers->integer ) maxyoffset += SCR_DrawSpectators( &ptr, xpos, ypos + yoffset, panelWidth, font, false, "Chasers", colorOrange, pass ); else SCR_IgnoreSpectators( &ptr, false ); } if ( yoffset > maxyoffset ) maxyoffset = yoffset; } if( !pass ) SCR_DrawPlayerIcons( font ); } // add the player stats yoffset = maxyoffset + trap_SCR_FontHeight( font ); yoffset += SCB_DrawPlayerStats( xpos, ypos + yoffset, monofont ); }
/* * SCR_DrawPlayerTab */ static int SCR_DrawPlayerTab( const char **ptrptr, int team, int x, int y, int panelWidth, struct qfontface_s *font, int pass ) { int dir, align, i, columncount; char type, string[MAX_STRING_CHARS]; const char *token, *layout; int height, width, xoffset, yoffset; vec4_t teamcolor = { 0.0f, 0.0f, 0.0f, 1.0f }, color; int iconnum; struct shader_s *icon; bool highlight = false, trans = false; if( GS_TeamBasedGametype() ) { dir = ( team == TEAM_ALPHA ) ? -1 : 1; align = ( team == TEAM_ALPHA ) ? ALIGN_RIGHT_TOP : ALIGN_LEFT_TOP; } else { dir = 0; align = ALIGN_CENTER_TOP; } xoffset = 0; yoffset = 0; height = trap_SCR_FontHeight( font ); // start from the center again xoffset = CG_HorizontalAlignForWidth( 0, align, panelWidth ); xoffset += ( SCB_CENTERMARGIN * dir ); // draw the background columncount = 0; if( ( team == TEAM_ALPHA ) || ( team == TEAM_BETA ) ) CG_TeamColor( team, teamcolor ); // draw the player tab column titles layout = cgs.configStrings[CS_SCB_PLAYERTAB_LAYOUT]; while( SCR_GetNextColumnLayout( &layout, NULL, &type, &width, font ) != NULL ) { // grab the actual scoreboard data if( !SCR_ParseToken( ptrptr, &token ) ) break; if( SCR_SkipColumn( type ) ) continue; Vector4Copy( colorWhite, color ); // reset to white after each column icon = NULL; string[0] = 0; // interpret the data based on the type defined in the layout switch( type ) { default: CG_Error( "SCR_DrawPlayerTab: Invalid player tab layout\n" ); break; case 's': // is a string { char l10n[MAX_STRING_CHARS]; Q_strncpyz( string, CG_TranslateColoredString( token, l10n, sizeof( l10n ) ), sizeof( string ) ); } break; case 'n': // is a player name indicated by player number i = atoi( token ); if( i < 0 ) // negative numbers toggle transparency on { trans = true; i = -1 - i; } if( i < 0 || i >= gs.maxclients ) Q_strncpyz( string, "invalid", sizeof( string ) ); else Q_strncpyz( string, cgs.clientInfo[i].name, sizeof( string ) ); if( ISVIEWERENTITY( i + 1 ) ) // highlight if it's our own player highlight = true; break; case 'i': // is a integer (negatives are colored in red) i = atoi( token ); Q_snprintfz( string, sizeof( string ), "%i", i ); VectorCopy( i >= 0 ? colorWhite : colorRed, color ); break; case 'f': // is a float Q_snprintfz( string, sizeof( string ), "%.2f", atof( token ) ); break; case 'l': // p is an integer colored in latency style i = atoi( token ); Q_snprintfz( string, sizeof( string ), "%i", i ); CG_PingColor( i, color ); break; case 'b': // is a Y/N boolean i = atoi( token ); Q_snprintfz( string, sizeof( string ), "%s", CG_TranslateString( ( i != 0 ) ? "Yes" : "No" ) ); VectorCopy( i ? colorGreen : colorRed, color ); break; case 'p': // is a picture. It uses height for width to get a square iconnum = atoi( token ); if( ( iconnum > 0 ) && ( iconnum < MAX_IMAGES ) ) icon = cgs.imagePrecache[iconnum]; break; case 't': // is a race time. Convert time into MM:SS:mm { unsigned int milli, min, sec; milli = (unsigned int)( atoi( token ) ); if( !milli ) Q_snprintfz( string, sizeof( string ), CG_TranslateString( "no time" ) ); else { min = milli / 60000; milli -= min * 60000; sec = milli / 1000; milli -= sec * 1000; Q_snprintfz( string, sizeof( string ), va( "%02i:%02i.%03i", min, sec, milli ) ); } } break; case 'r': // is a ready state tick that is hidden when not in warmup if( atoi( token ) ) icon = CG_MediaShader( cgs.media.shaderVSayIcon[VSAY_YES] ); break; } if( !width ) continue; // draw the column background teamcolor[3] = SCB_BACKGROUND_ALPHA; if( columncount & 1 ) teamcolor[3] -= 0.15; if( highlight ) teamcolor[3] += 0.3; if( trans ) color[3] = 0.3; if( !pass ) { trap_R_DrawStretchPic( x + xoffset, y + yoffset, width, height, 0, 0, 1, 1, teamcolor, cgs.shaderWhite ); if( icon ) SCR_AddPlayerIcon( icon, x + xoffset, y + yoffset, color[3], font ); } // draw the column value if( pass && string[0] ) { trap_SCR_DrawClampString( x + xoffset, y + yoffset, string, x + xoffset, y + yoffset, x + xoffset + width, y + yoffset + height, font, color ); } columncount++; xoffset += width; } yoffset += height; return yoffset; }
/* * SCR_DrawTeamTab */ static int SCR_DrawTeamTab( const char **ptrptr, int *curteam, int x, int y, int panelWidth, struct qfontface_s *font, struct qfontface_s *titleFont, int pass ) { const char *token; const char *layout, *titles; char type; int team, team_score, team_ping; int yoffset = 0, xoffset = 0; int dir = 0, align, width, height; vec4_t teamcolor = { 0.0f, 0.0f, 0.0f, 1.0f }, pingcolor; // team tab is always the same. Sets the current team and draws its score if( !(*ptrptr) || !(*ptrptr[0]) || *ptrptr[0] == '&' ) return yoffset; team = CG_ParseValue( ptrptr ); if( team < TEAM_PLAYERS || team > TEAM_BETA ) CG_Error( "SCR_ParseTeamTab: Invalid team value\n" ); *curteam = team; if( *ptrptr[0] == '&' ) return yoffset; team_score = CG_ParseValue( ptrptr ); if( *ptrptr[0] == '&' ) return yoffset; team_ping = CG_ParseValue( ptrptr ); if( ( team == TEAM_ALPHA ) || ( team == TEAM_BETA ) ) CG_TeamColor( team, teamcolor ); teamcolor[3] = SCB_BACKGROUND_ALPHA; // make transparent if( GS_TeamBasedGametype() ) // we only draw the team tabs in team based gametypes { dir = ( team == TEAM_ALPHA ) ? -1 : 1; align = ( team == TEAM_ALPHA ) ? ALIGN_RIGHT_TOP : ALIGN_LEFT_TOP; // draw the tab xoffset = ( SCB_CENTERMARGIN * dir ); width = ( cgs.vidWidth * 0.5 ) - SCB_CENTERMARGIN; height = trap_SCR_FontHeight( titleFont ) + 2; if( !pass ) { CG_DrawAlignPic( x + xoffset, y + yoffset + SCB_SCORENUMBER_SIZE - height, width, height, align, teamcolor, cgs.shaderWhite ); } if( pass ) { xoffset += ( ( 16 * cgs.vidHeight / 600 ) * dir ); CG_DrawHUDNumeric( x + xoffset, y + yoffset, align, colorWhite, SCB_SCORENUMBER_SIZE, SCB_SCORENUMBER_SIZE, team_score ); xoffset += ( ( SCB_SCORENUMBER_SIZE * strlen(va("%i", team_score)) + ( 16 * cgs.vidHeight / 600 ) ) * dir ); trap_SCR_DrawStringWidth( x + xoffset + ( ( SCB_TINYFIELD_PIXELWIDTH + ( 16 * cgs.vidHeight / 600 ) ) * dir ), y + yoffset + SCB_SCORENUMBER_SIZE - (trap_SCR_FontHeight( titleFont ) + 1), align, GS_TeamName( team ), SCB_TEAMNAME_PIXELWIDTH, titleFont, colorWhite ); CG_PingColor( team_ping, pingcolor ); trap_SCR_DrawStringWidth( x + xoffset, y + yoffset + SCB_SCORENUMBER_SIZE - (trap_SCR_FontHeight( font ) + 1), align, va( "%i", team_ping ), SCB_TINYFIELD_PIXELWIDTH, font, pingcolor ); } yoffset += SCB_SCORENUMBER_SIZE; } else { dir = 0; align = ALIGN_CENTER_TOP; } // draw the player tab column titles layout = cgs.configStrings[CS_SCB_PLAYERTAB_LAYOUT]; titles = cgs.configStrings[CS_SCB_PLAYERTAB_TITLES]; height = trap_SCR_FontHeight( font ); // start from the center again xoffset = CG_HorizontalAlignForWidth( 0, align, panelWidth ); xoffset += ( SCB_CENTERMARGIN * dir ); while( ( token = SCR_GetNextColumnLayout( &layout, &titles, &type, &width, font ) ) != NULL ) { if( SCR_SkipColumn( type ) ) continue; if( width ) { if( pass ) { trap_SCR_DrawClampString( x + xoffset, y + yoffset, CG_TranslateString( token ), x + xoffset, y + yoffset, x + xoffset + width, y + yoffset + height, font, colorWhite ); } xoffset += width; } } yoffset += trap_SCR_FontHeight( font ); return yoffset; }
/* * SCR_DrawSpectators */ static int SCR_DrawSpectators( const char **ptrptr, int x, int y, int panelWidth, struct qfontface_s *font, bool havePing, const char *title, vec4_t titleColor, int pass ) { const char *token; char string[MAX_STRING_CHARS]; int yoffset = 0, xoffset = 0; int playerNum, ping; int aligns[3], offsets[3]; int colwidth, fullwidth, count = 0, height; bool titleDrawn = false; fullwidth = panelWidth * 1.5; if( fullwidth > cgs.vidWidth * 0.7 ) fullwidth = cgs.vidWidth * 0.7; colwidth = fullwidth / 3; aligns[0] = ALIGN_CENTER_TOP; aligns[1] = ALIGN_LEFT_TOP; aligns[2] = ALIGN_RIGHT_TOP; offsets[0] = 0; offsets[1] = -fullwidth * 0.5; offsets[2] = fullwidth * 0.5; assert( ptrptr && *ptrptr ); height = trap_SCR_FontHeight( font ); yoffset = height; // draw spectators while( *ptrptr ) { if( !SCR_ParseToken( ptrptr, &token ) ) break; // first token is played id playerNum = atoi( token ); if( playerNum < 0 || playerNum >= gs.maxclients ) break; if( havePing ) { // get a second token if( !SCR_ParseToken( ptrptr, &token ) ) break; // second token is ping ping = atoi( token ); // draw the spectator if( ping < 0 ) Q_snprintfz( string, sizeof( string ), "%s%s ...", cgs.clientInfo[playerNum].name, S_COLOR_WHITE ); else Q_snprintfz( string, sizeof( string ), "%s%s %i", cgs.clientInfo[playerNum].name, S_COLOR_WHITE, ping ); } else { Q_snprintfz( string, sizeof( string ), "%s%s", cgs.clientInfo[playerNum].name, S_COLOR_WHITE ); } // draw title if there are any spectators if( !titleDrawn ) { titleDrawn = true; if( pass ) { trap_SCR_DrawString( x, y + yoffset, ALIGN_CENTER_TOP, CG_TranslateString( title ), font, titleColor ); } yoffset += height; } xoffset = offsets[count] + CG_HorizontalAlignForWidth( 0, aligns[count], trap_SCR_strWidth( string, font, 0 ) ); if ( pass ) { // fixme: the boxes aren't actually correctly aligned trap_SCR_DrawClampString( x + xoffset, y + yoffset, string, x + xoffset, y + yoffset, x + xoffset + colwidth, y + yoffset + height, font, colorWhite ); } count++; if( count > 2 ) { count = 0; yoffset += height; } } if( count ) yoffset += height; return yoffset; }
/* * SCB_DrawPlayerStats */ static int SCB_DrawPlayerStats( int x, int y, struct qfontface_s *font ) { int xoffset, yoffset, lines; int i, j, num_weapons, weap, xpos, width, done; gsitem_t *it; char string[MAX_STRING_CHARS]; vec4_t color = { 0.5, 0.5, 0.5, 0.5f }; // don't display stats if( !cg_scoreboardStats->integer ) return 0; // total number of weapon num_weapons = WEAP_TOTAL-WEAP_GUNBLADE; width = ( SCB_TINYFIELD_PIXELWIDTH + 2 * SCB_SMALLFIELD_PIXELWIDTH ) * 2 + SCB_SMALLFIELD_PIXELWIDTH; xpos = -width / 2; // Center the box xoffset = xpos; yoffset = trap_SCR_FontHeight( font ); // Room for header, it's actually written later if we have at least one stat yoffset += trap_SCR_FontHeight( font ); lines = 0; for( i = 0; i < num_weapons; ) { xoffset = xpos; // two weapons per line for( j = 0, done = 0; done < 2 && i + j < num_weapons; j++ ) { weap = WEAP_GUNBLADE + i + j; if( scb_player_stats[2*( i+j )] == -1 && scb_player_stats[2*( i+j )+1] == -1 ) continue; it = GS_FindItemByTag( weap ); // short name Q_snprintfz( string, sizeof( string ), "%s%2s", it->color, it->shortname ); trap_SCR_DrawStringWidth( x + xoffset, y + yoffset, ALIGN_LEFT_TOP, string, SCB_TINYFIELD_PIXELWIDTH, font, colorWhite ); Q_snprintfz( string, sizeof( string ), "%2d%c", scb_player_stats[2*( i+j )+1], '%' ); trap_SCR_DrawStringWidth( x + xoffset + 2 * SCB_TINYFIELD_PIXELWIDTH, y + yoffset, ALIGN_CENTER_TOP, string, 2*SCB_SMALLFIELD_PIXELWIDTH, font, colorWhite ); // separator xoffset = 0; done++; } // next line if( done > 0 ) { lines++; yoffset += trap_SCR_FontHeight( font ); } i += j; } if( lines ) { // if we drew anything, draw header and box too xoffset = xpos; yoffset = trap_SCR_FontHeight( font ); // header trap_SCR_DrawStringWidth( x + xoffset, y + yoffset, ALIGN_LEFT_TOP, CG_TranslateString( "Weapon stats" ), width, font, colorMdGrey ); yoffset += trap_SCR_FontHeight( font ); // box trap_R_DrawStretchPic( x + xoffset - SCB_TINYFIELD_PIXELWIDTH/2, y + yoffset, width + SCB_TINYFIELD_PIXELWIDTH, lines * trap_SCR_FontHeight( font ), 0, 0, 1, 1, color, cgs.shaderWhite ); return ( trap_SCR_FontHeight( font ) * ( 2+lines ) ); } return 0; }
/* ** CG_DrawChat */ void CG_DrawChat( cg_gamechat_t *chat, int x, int y, char *fontName, struct qfontface_s *font, int fontSize, int width, int height, int padding_x, int padding_y, vec4_t backColor, struct shader_s *backShader ) { int i, j; int s, e, w; int utf_len; int l, total_lines, lines; int x_offset, y_offset; int font_height; int pass; int lastcolor; int message_mode; int wait_time, fade_time; const cg_gamemessage_t *msg; const char *text; char tstr[GAMECHAT_STRING_SIZE]; vec4_t fontColor; bool chat_active = false; bool background_drawn = false; int corner_radius = 12 * cgs.vidHeight / 600; int background_y; int first_candidate; font_height = trap_SCR_FontHeight( font ); message_mode = (int)trap_Cvar_Value( "con_messageMode" ); chat_active = ( chat->lastMsgTime + GAMECHAT_WAIT_IN_TIME + GAMECHAT_FADE_IN_TIME > cg.realTime || message_mode ); lines = 0; total_lines = /*!message_mode ? 0 : */ 1; if( chat_active ) { wait_time = GAMECHAT_WAIT_IN_TIME; fade_time = GAMECHAT_FADE_IN_TIME; } else { wait_time = GAMECHAT_WAIT_OUT_TIME; fade_time = GAMECHAT_FADE_OUT_TIME; } if( chat_active != chat->lastActive ) { // smooth fade ins and fade outs chat->lastActiveChangeTime = cg.realTime - ( 1.0 - chat->activeFrac ) * ( wait_time + fade_time ); } if( cg.realTime >= chat->lastActiveChangeTime + wait_time ) { int time_diff, time_interval; time_diff = cg.realTime - ( chat->lastActiveChangeTime + wait_time ); time_interval = fade_time; if( time_diff <= time_interval ) { chat->activeFrac = (float)time_diff / time_interval; } else { chat->activeFrac = 1; } } else { chat->activeFrac = 0; } if( chat_active ) { backColor[3] *= chat->activeFrac; } else { backColor[3] *= ( 1.0 - chat->activeFrac ); } for( i = 0; i < GAMECHAT_STACK_SIZE; i++ ) { bool old_msg; l = chat->nextMsg - 1 - i; if( l < 0 ) { l = GAMECHAT_STACK_SIZE + l; } msg = &chat->messages[l]; text = msg->text; old_msg = !message_mode && ( cg.realTime > msg->time + GAMECHAT_NOTIFY_TIME ); if( !background_drawn && backColor[3] ) { if( old_msg ) { // keep the box being drawn for a while to prevent it from flickering // upon arrival of the possibly entered chat message if( !( !chat_active && cg.realTime <= chat->lastActiveChangeTime + 200 ) ) { break; } } background_y = y; trap_R_DrawStretchPic( x, background_y, width, height - corner_radius, 0.0f, 0.0f, 1.0f, 0.5f, backColor, backShader ); background_y += height - corner_radius; if( trap_IN_IME_GetCandidates( NULL, 0, 10, NULL, &first_candidate ) ) { int candidates_height = ( first_candidate ? 3 : 5 ) * font_height; trap_R_DrawStretchPic( x, background_y, width, candidates_height, 0.0f, 0.5f, 1.0f, 0.5f, backColor, backShader ); background_y += candidates_height; } trap_R_DrawStretchPic( x, background_y, corner_radius, corner_radius, 0.0f, 0.5f, 0.5f, 1.0f, backColor, backShader ); trap_R_DrawStretchPic( x + corner_radius, background_y, width - corner_radius * 2, corner_radius, 0.5f, 0.5f, 0.5f, 1.0f, backColor, backShader ); trap_R_DrawStretchPic( x + width - corner_radius, background_y, corner_radius, corner_radius, 0.5f, 0.5f, 1.0f, 1.0f, backColor, backShader ); background_drawn = true; } // unless user is typing something, only display recent messages if( old_msg ) { break; } pass = 0; lines = 0; lastcolor = ColorIndex( COLOR_WHITE ); parse_string: l = 1; s = e = 0; while( 1 ) { int len; memset( tstr, 0, sizeof( tstr ) ); // skip whitespaces at start for( ; text[s] == '\n' || Q_IsBreakingSpace( text + s ); s = Q_Utf8SyncPos( text, s + 1, UTF8SYNC_RIGHT ) ) ; // empty string if( !text[s] ) { break; } w = -1; len = trap_SCR_StrlenForWidth( text + s, font, width - padding_x * 2 ); clamp_low( len, 1 ); for( j = s; ( j < ( s + len ) ) && text[j] != '\0'; j += utf_len ) { utf_len = Q_Utf8SyncPos( text + j, 1, UTF8SYNC_RIGHT ); memcpy( tstr + j - s, text + j, utf_len ); if( text[j] == '\n' || Q_IsBreakingSpace( text + j ) ) { w = j; // last whitespace } if( text[j] == '\n' ) { break; } } e = j; // end // try to word avoid splitting words, unless no other options if( text[j] != '\0' && w > 0 ) { // stop at the last encountered whitespace j = w; } tstr[j - s] = '\0'; Vector4Copy( color_table[lastcolor], fontColor ); fontColor[3] = chat_active ? chat->activeFrac : 1.0 - chat->activeFrac; if( pass ) { // now actually render the line x_offset = padding_x; y_offset = height - padding_y - font_height - ( total_lines + lines - l ) * ( font_height + 2 ); if( y_offset < padding_y ) { break; } trap_SCR_DrawClampString( x + x_offset, y + y_offset, tstr, x + padding_x, y + padding_y, x - padding_x + width, y - padding_y + height, font, fontColor ); l++; } else { // increase the lines counter lines++; } if( !text[j] ) { // fast path: we don't need two passes in case of one-liners.. if( lines == 1 ) { x_offset = padding_x; y_offset = height - font_height - total_lines * ( font_height + 2 ); if( y_offset < padding_y ) { break; } trap_SCR_DrawClampString( x + x_offset, y + y_offset, tstr, x + padding_x, y + padding_y, x - padding_x + width, y - padding_y + height, font, fontColor ); total_lines++; pass++; } break; } if( pass ) { // grab the last color token to carry it over to the next line lastcolor = Q_ColorStrLastColor( lastcolor, tstr, j - s ); } s = j; } if( !pass ) { pass++; goto parse_string; } else { total_lines += lines; } } // let the engine know where the input line should be drawn trap_SCR_DrawChat( x + padding_x, y + height - padding_y - font_height, width - padding_x, font ); chat->lastActive = chat_active; }
/* * CG_DrawDemocam2D */ void CG_DrawDemocam2D( void ) { int xpos, ypos; const char *cam_type_name; int64_t cam_timestamp; char sfov[8], strack[8]; cg_subtitle_t *sub; if( !cgs.demoPlaying ) { return; } if( ( sub = CG_Democam_FindCurrentSubtitle() ) != NULL ) { if( sub->text && sub->text[0] ) { int y; if( sub->highprint ) { y = cgs.vidHeight * 0.30f; } else { y = cgs.vidHeight - ( cgs.vidHeight * 0.30f ); } CG_Democam_DrawCenterSubtitle( y, cgs.vidWidth * 0.75, cgs.fontSystemBig, sub->text ); } } if( democam_editing_mode ) { // draw the numbers of every entity in the view CG_DrawEntityNumbers(); // draw the cams info xpos = 8 * cgs.vidHeight / 600; ypos = 100 * cgs.vidHeight / 600; if( *cgs.demoName ) { trap_SCR_DrawString( xpos, ypos, ALIGN_LEFT_TOP, va( "Demo: %s", cgs.demoName ), cgs.fontSystemSmall, colorWhite ); ypos += trap_SCR_FontHeight( cgs.fontSystemSmall ); } trap_SCR_DrawString( xpos, ypos, ALIGN_LEFT_TOP, va( "Play mode: %s%s%s", S_COLOR_ORANGE, CamIsFree ? "Free Fly" : "Preview", S_COLOR_WHITE ), cgs.fontSystemSmall, colorWhite ); ypos += trap_SCR_FontHeight( cgs.fontSystemSmall ); trap_SCR_DrawString( xpos, ypos, ALIGN_LEFT_TOP, va( "Time: %" PRIi64, demo_time ), cgs.fontSystemSmall, colorWhite ); ypos += trap_SCR_FontHeight( cgs.fontSystemSmall ); cam_type_name = "none"; cam_timestamp = 0; if( currentcam ) { cam_type_name = cam_TypeNames[currentcam->type]; cam_timestamp = currentcam->timeStamp; Q_snprintfz( strack, sizeof( strack ), "%i", currentcam->trackEnt ); Q_snprintfz( sfov, sizeof( sfov ), "%i", currentcam->fov ); } else { Q_strncpyz( strack, "NO", sizeof( strack ) ); Q_strncpyz( sfov, "NO", sizeof( sfov ) ); } trap_SCR_DrawString( xpos, ypos, ALIGN_LEFT_TOP, va( "Current cam: " S_COLOR_ORANGE "%s" S_COLOR_WHITE " Fov " S_COLOR_ORANGE "%s" S_COLOR_WHITE " Start %" PRIi64 " Tracking " S_COLOR_ORANGE "%s" S_COLOR_WHITE, cam_type_name, sfov, cam_timestamp, strack ), cgs.fontSystemSmall, colorWhite ); ypos += trap_SCR_FontHeight( cgs.fontSystemSmall ); if( currentcam ) { trap_SCR_DrawString( xpos, ypos, ALIGN_LEFT_TOP, va( "Pitch: " S_COLOR_ORANGE "%.2f" S_COLOR_WHITE " Yaw: " S_COLOR_ORANGE "%.2f" S_COLOR_WHITE " Roll: " S_COLOR_ORANGE "%.2f" S_COLOR_WHITE, currentcam->angles[PITCH], currentcam->angles[YAW], currentcam->angles[ROLL] ), cgs.fontSystemSmall, colorWhite ); } ypos += trap_SCR_FontHeight( cgs.fontSystemSmall ); cam_type_name = "none"; cam_timestamp = 0; Q_strncpyz( sfov, "NO", sizeof( sfov ) ); if( nextcam ) { cam_type_name = cam_TypeNames[nextcam->type]; cam_timestamp = nextcam->timeStamp; Q_snprintfz( strack, sizeof( strack ), "%i", nextcam->trackEnt ); Q_snprintfz( sfov, sizeof( sfov ), "%i", nextcam->fov ); } else { Q_strncpyz( strack, "NO", sizeof( strack ) ); Q_strncpyz( sfov, "NO", sizeof( sfov ) ); } trap_SCR_DrawString( xpos, ypos, ALIGN_LEFT_TOP, va( "Next cam: " S_COLOR_ORANGE "%s" S_COLOR_WHITE " Fov " S_COLOR_ORANGE "%s" S_COLOR_WHITE " Start %" PRIi64 " Tracking " S_COLOR_ORANGE "%s" S_COLOR_WHITE, cam_type_name, sfov, cam_timestamp, strack ), cgs.fontSystemSmall, colorWhite ); ypos += trap_SCR_FontHeight( cgs.fontSystemSmall ); if( nextcam ) { trap_SCR_DrawString( xpos, ypos, ALIGN_LEFT_TOP, va( "Pitch: " S_COLOR_ORANGE "%.2f" S_COLOR_WHITE " Yaw: " S_COLOR_ORANGE "%.2f" S_COLOR_WHITE " Roll: " S_COLOR_ORANGE "%.2f" S_COLOR_WHITE, nextcam->angles[PITCH], nextcam->angles[YAW], nextcam->angles[ROLL] ), cgs.fontSystemSmall, colorWhite ); } ypos += trap_SCR_FontHeight( cgs.fontSystemSmall ); } }
void CG_Democam_DrawCenterSubtitle( int y, unsigned int maxwidth, struct qfontface_s *font, char *text ) { char *ptr, *s, *t, c, d; int x = cgs.vidWidth / 2; if( !text || !text[0] ) { return; } int shadowOffset = 2 * cgs.vidHeight / 600; if( !shadowOffset ) { shadowOffset = 1; } if( !maxwidth || trap_SCR_strWidth( text, font, 0 ) <= maxwidth ) { trap_SCR_DrawStringWidth( x + shadowOffset, y + shadowOffset, ALIGN_CENTER_TOP, COM_RemoveColorTokens( text ), maxwidth, font, colorBlack ); trap_SCR_DrawStringWidth( x, y, ALIGN_CENTER_TOP, text, maxwidth, font, colorWhite ); return; } t = s = ptr = text; while( *s ) { while( *s && *s != ' ' && *s != '\n' ) s++; if( ( !*s || *s == '\n' ) && trap_SCR_strWidth( ptr, font, 0 ) < maxwidth ) { // new line or end of text, in both cases force write c = *s; *s = 0; trap_SCR_DrawStringWidth( x + shadowOffset, y + shadowOffset, ALIGN_CENTER_TOP, COM_RemoveColorTokens( ptr ), maxwidth, font, colorBlack ); trap_SCR_DrawStringWidth( x, y, ALIGN_CENTER_TOP, ptr, maxwidth, font, colorWhite ); *s = c; if( !*s ) { break; } t = s; s++; ptr = s; } else { c = *s; *s = 0; if( trap_SCR_strWidth( ptr, font, 0 ) < maxwidth ) { *s = c; t = s; s++; continue; } *s = c; d = *t; *t = 0; trap_SCR_DrawStringWidth( x + shadowOffset, y + shadowOffset, ALIGN_CENTER_TOP, COM_RemoveColorTokens( ptr ), maxwidth, font, colorBlack ); trap_SCR_DrawStringWidth( x, y, ALIGN_CENTER_TOP, ptr, maxwidth, font, colorWhite ); *t = d; s = t; s++; ptr = s; } y += trap_SCR_FontHeight( font ); } }
/* * SCR_DrawChallengers */ static int SCR_DrawChallengers( const char **ptrptr, int x, int y, int panelWidth, struct qfontface_s *font, int pass ) { char *token; const char *oldptr; char string[MAX_STRING_CHARS]; int yoffset = 0, xoffset = 0; int playerNum, ping; int height; assert( ptrptr && *ptrptr ); height = trap_SCR_FontHeight( font ); // draw title yoffset = height; if( pass ) { trap_SCR_DrawString( x + xoffset, y + yoffset, ALIGN_CENTER_TOP, CG_TranslateString( "Spectating you" ), font, colorCyan ); // racesow } yoffset += height; // draw challengers while( *ptrptr ) { oldptr = *ptrptr; token = COM_ParseExt( ptrptr, true ); if( !token[0] ) break; if( token[0] == '&' ) // it's a different command than 'challengers', so step back and return { *ptrptr = oldptr; break; } // first token is played id playerNum = atoi( token ); if( playerNum < 0 || playerNum >= gs.maxclients ) break; // get a second token oldptr = *ptrptr; token = COM_ParseExt( ptrptr, true ); if( !token[0] ) break; if( token[0] == '&' ) // it's a different command than 'challengers', so step back and return { *ptrptr = oldptr; break; } // second token is ping ping = atoi( token ); // draw the challenger if( ping < 0 ) Q_snprintfz( string, sizeof( string ), "%s%s ...", cgs.clientInfo[playerNum].name, S_COLOR_WHITE ); else Q_snprintfz( string, sizeof( string ), "%s%s %i", cgs.clientInfo[playerNum].name, S_COLOR_WHITE, ping ); if( pass ) { trap_SCR_DrawString( x + xoffset, y + yoffset, ALIGN_CENTER_TOP, string, font, colorWhite ); } yoffset += height; } yoffset += height; return yoffset; }