File: client\cl_scrn.c

    1 /*
    2 Copyright (C) 1997-2001 Id Software, Inc.
    3 
    4 This program is free software; you can redistribute it and/or
    5 modify it under the terms of the GNU General Public License
    6 as published by the Free Software Foundation; either version 2
    7 of the License, or (at your option) any later version.
    8 
    9 This program is distributed in the hope that it will be useful,
   10 but WITHOUT ANY WARRANTY; without even the implied warranty of
   11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
   12 
   13 See the GNU General Public License for more details.
   14 
   15 You should have received a copy of the GNU General Public License
   16 along with this program; if not, write to the Free Software
   17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
   18 
   19 */
   20 // cl_scrn.c -- master for refresh, status bar, console, chat, notify, etc
   21 
   22 /*
   23 
   24   full screen console
   25   put up loading plaque
   26   blanked background with loading plaque
   27   blanked background with menu
   28   cinematics
   29   full screen image for quit and victory
   30 
   31   end of unit intermissions
   32 
   33   */
   34 
   35 #include "client.h"
   36 
   37 float           scr_con_current;        // aproaches scr_conlines at scr_conspeed
   38 float           scr_conlines;           // 0.0 to 1.0 lines of console to display
   39 
   40 qboolean        scr_initialized;                // ready to draw
   41 
   42 int                     scr_draw_loading;
   43 
   44 vrect_t         scr_vrect;              // position of render window on screen
   45 
   46 
   47 cvar_t          *scr_viewsize;
   48 cvar_t          *scr_conspeed;
   49 cvar_t          *scr_centertime;
   50 cvar_t          *scr_showturtle;
   51 cvar_t          *scr_showpause;
   52 cvar_t          *scr_printspeed;
   53 
   54 cvar_t          *scr_netgraph;
   55 cvar_t          *scr_timegraph;
   56 cvar_t          *scr_debuggraph;
   57 cvar_t          *scr_graphheight;
   58 cvar_t          *scr_graphscale;
   59 cvar_t          *scr_graphshift;
   60 cvar_t          *scr_drawall;
   61 
   62 typedef struct
   63 {
   64         int             x1, y1, x2, y2;
   65 } dirty_t;
   66 
   67 dirty_t         scr_dirty, scr_old_dirty[2];
   68 
   69 char            crosshair_pic[MAX_QPATH];
   70 int                     crosshair_width, crosshair_height;
   71 
   72 void SCR_TimeRefresh_f (void);
   73 void SCR_Loading_f (void);
   74 
   75 
   76 /*
   77 ===============================================================================
   78 
   79 BAR GRAPHS
   80 
   81 ===============================================================================
   82 */
   83 
   84 /*
   85 ==============
   86 CL_AddNetgraph
   87 
   88 A new packet was just parsed
   89 ==============
   90 */
   91 void CL_AddNetgraph (void)
   92 {
   93         int             i;
   94         int             in;
   95         int             ping;
   96 
   97         // if using the debuggraph for something else, don't
   98         // add the net lines
   99         if (scr_debuggraph->value || scr_timegraph->value)
  100                 return;
  101 
  102         for (i=0 ; i<cls.netchan.dropped ; i++)
  103                 SCR_DebugGraph (30, 0x40);
  104 
  105         for (i=0 ; i<cl.surpressCount ; i++)
  106                 SCR_DebugGraph (30, 0xdf);
  107 
  108         // see what the latency was on this packet
  109         in = cls.netchan.incoming_acknowledged & (CMD_BACKUP-1);
  110         ping = cls.realtime - cl.cmd_time[in];
  111         ping /= 30;
  112         if (ping > 30)
  113                 ping = 30;
  114         SCR_DebugGraph (ping, 0xd0);
  115 }
  116 
  117 
  118 typedef struct
  119 {
  120         float   value;
  121         int             color;
  122 } graphsamp_t;
  123 
  124 static  int                     current;
  125 static  graphsamp_t     values[1024];
  126 
  127 /*
  128 ==============
  129 SCR_DebugGraph
  130 ==============
  131 */
  132 void SCR_DebugGraph (float value, int color)
  133 {
  134         values[current&1023].value = value;
  135         values[current&1023].color = color;
  136         current++;
  137 }
  138 
  139 /*
  140 ==============
  141 SCR_DrawDebugGraph
  142 ==============
  143 */
  144 void SCR_DrawDebugGraph (void)
  145 {
  146         int             a, x, y, w, i, h;
  147         float   v;
  148         int             color;
  149 
  150         //
  151         // draw the graph
  152         //
  153         w = scr_vrect.width;
  154 
  155         x = scr_vrect.x;
  156         y = scr_vrect.y+scr_vrect.height;
  157         re.DrawFill (x, y-scr_graphheight->value,
  158                 w, scr_graphheight->value, 8);
  159 
  160         for (a=0 ; a<w ; a++)
  161         {
  162                 i = (current-1-a+1024) & 1023;
  163                 v = values[i].value;
  164                 color = values[i].color;
  165                 v = v*scr_graphscale->value + scr_graphshift->value;
  166                 
  167                 if (v < 0)
  168                         v += scr_graphheight->value * (1+(int)(-v/scr_graphheight->value));
  169                 h = (int)v % (int)scr_graphheight->value;
  170                 re.DrawFill (x+w-1-a, y - h, 1, h, color);
  171         }
  172 }
  173 
  174 /*
  175 ===============================================================================
  176 
  177 CENTER PRINTING
  178 
  179 ===============================================================================
  180 */
  181 
  182 char            scr_centerstring[1024];
  183 float           scr_centertime_start;   // for slow victory printing
  184 float           scr_centertime_off;
  185 int                     scr_center_lines;
  186 int                     scr_erase_center;
  187 
  188 /*
  189 ==============
  190 SCR_CenterPrint
  191 
  192 Called for important messages that should stay in the center of the screen
  193 for a few moments
  194 ==============
  195 */
  196 void SCR_CenterPrint (char *str)
  197 {
  198         char    *s;
  199         char    line[64];
  200         int             i, j, l;
  201 
  202         strncpy (scr_centerstring, str, sizeof(scr_centerstring)-1);
  203         scr_centertime_off = scr_centertime->value;
  204         scr_centertime_start = cl.time;
  205 
  206         // count the number of lines for centering
  207         scr_center_lines = 1;
  208         s = str;
  209         while (*s)
  210         {
  211                 if (*s == '\n')
  212                         scr_center_lines++;
  213                 s++;
  214         }
  215 
  216         // echo it to the console
  217         Com_Printf("\n\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\37\n\n");
  218 
  219         s = str;
  220         do      
  221         {
  222         // scan the width of the line
  223                 for (l=0 ; l<40 ; l++)
  224                         if (s[l] == '\n' || !s[l])
  225                                 break;
  226                 for (i=0 ; i<(40-l)/2 ; i++)
  227                         line[i] = ' ';
  228 
  229                 for (j=0 ; j<l ; j++)
  230                 {
  231                         line[i++] = s[j];
  232                 }
  233 
  234                 line[i] = '\n';
  235                 line[i+1] = 0;
  236 
  237                 Com_Printf ("%s", line);
  238 
  239                 while (*s && *s != '\n')
  240                         s++;
  241 
  242                 if (!*s)
  243                         break;
  244                 s++;            // skip the \n
  245         } while (1);
  246         Com_Printf("\n\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\37\n\n");
  247         Con_ClearNotify ();
  248 }
  249 
  250 
  251 void SCR_DrawCenterString (void)
  252 {
  253         char    *start;
  254         int             l;
  255         int             j;
  256         int             x, y;
  257         int             remaining;
  258 
  259 // the finale prints the characters one at a time
  260         remaining = 9999;
  261 
  262         scr_erase_center = 0;
  263         start = scr_centerstring;
  264 
  265         if (scr_center_lines <= 4)
  266                 y = viddef.height*0.35;
  267         else
  268                 y = 48;
  269 
  270         do      
  271         {
  272         // scan the width of the line
  273                 for (l=0 ; l<40 ; l++)
  274                         if (start[l] == '\n' || !start[l])
  275                                 break;
  276                 x = (viddef.width - l*8)/2;
  277                 SCR_AddDirtyPoint (x, y);
  278                 for (j=0 ; j<l ; j++, x+=8)
  279                 {
  280                         re.DrawChar (x, y, start[j]);   
  281                         if (!remaining--)
  282                                 return;
  283                 }
  284                 SCR_AddDirtyPoint (x, y+8);
  285                         
  286                 y += 8;
  287 
  288                 while (*start && *start != '\n')
  289                         start++;
  290 
  291                 if (!*start)
  292                         break;
  293                 start++;                // skip the \n
  294         } while (1);
  295 }
  296 
  297 void SCR_CheckDrawCenterString (void)
  298 {
  299         scr_centertime_off -= cls.frametime;
  300         
  301         if (scr_centertime_off <= 0)
  302                 return;
  303 
  304         SCR_DrawCenterString ();
  305 }
  306 
  307 //=============================================================================
  308 
  309 /*
  310 =================
  311 SCR_CalcVrect
  312 
  313 Sets scr_vrect, the coordinates of the rendered window
  314 =================
  315 */
  316 static void SCR_CalcVrect (void)
  317 {
  318         int             size;
  319 
  320         // bound viewsize
  321         if (scr_viewsize->value < 40)
  322                 Cvar_Set ("viewsize","40");
  323         if (scr_viewsize->value > 100)
  324                 Cvar_Set ("viewsize","100");
  325 
  326         size = scr_viewsize->value;
  327 
  328         scr_vrect.width = viddef.width*size/100;
  329         scr_vrect.width &= ~7;
  330 
  331         scr_vrect.height = viddef.height*size/100;
  332         scr_vrect.height &= ~1;
  333 
  334         scr_vrect.x = (viddef.width - scr_vrect.width)/2;
  335         scr_vrect.y = (viddef.height - scr_vrect.height)/2;
  336 }
  337 
  338 
  339 /*
  340 =================
  341 SCR_SizeUp_f
  342 
  343 Keybinding command
  344 =================
  345 */
  346 void SCR_SizeUp_f (void)
  347 {
  348         Cvar_SetValue ("viewsize",scr_viewsize->value+10);
  349 }
  350 
  351 
  352 /*
  353 =================
  354 SCR_SizeDown_f
  355 
  356 Keybinding command
  357 =================
  358 */
  359 void SCR_SizeDown_f (void)
  360 {
  361         Cvar_SetValue ("viewsize",scr_viewsize->value-10);
  362 }
  363 
  364 /*
  365 =================
  366 SCR_Sky_f
  367 
  368 Set a specific sky and rotation speed
  369 =================
  370 */
  371 void SCR_Sky_f (void)
  372 {
  373         float   rotate;
  374         vec3_t  axis;
  375 
  376         if (Cmd_Argc() < 2)
  377         {
  378                 Com_Printf ("Usage: sky <basename> <rotate> <axis x y z>\n");
  379                 return;
  380         }
  381         if (Cmd_Argc() > 2)
  382                 rotate = atof(Cmd_Argv(2));
  383         else
  384                 rotate = 0;
  385         if (Cmd_Argc() == 6)
  386         {
  387                 axis[0] = atof(Cmd_Argv(3));
  388                 axis[1] = atof(Cmd_Argv(4));
  389                 axis[2] = atof(Cmd_Argv(5));
  390         }
  391         else
  392         {
  393                 axis[0] = 0;
  394                 axis[1] = 0;
  395                 axis[2] = 1;
  396         }
  397 
  398         re.SetSky (Cmd_Argv(1), rotate, axis);
  399 }
  400 
  401 //============================================================================
  402 
  403 /*
  404 ==================
  405 SCR_Init
  406 ==================
  407 */
  408 void SCR_Init (void)
  409 {
  410         scr_viewsize = Cvar_Get ("viewsize", "100", CVAR_ARCHIVE);
  411         scr_conspeed = Cvar_Get ("scr_conspeed", "3", 0);
  412         scr_showturtle = Cvar_Get ("scr_showturtle", "0", 0);
  413         scr_showpause = Cvar_Get ("scr_showpause", "1", 0);
  414         scr_centertime = Cvar_Get ("scr_centertime", "2.5", 0);
  415         scr_printspeed = Cvar_Get ("scr_printspeed", "8", 0);
  416         scr_netgraph = Cvar_Get ("netgraph", "0", 0);
  417         scr_timegraph = Cvar_Get ("timegraph", "0", 0);
  418         scr_debuggraph = Cvar_Get ("debuggraph", "0", 0);
  419         scr_graphheight = Cvar_Get ("graphheight", "32", 0);
  420         scr_graphscale = Cvar_Get ("graphscale", "1", 0);
  421         scr_graphshift = Cvar_Get ("graphshift", "0", 0);
  422         scr_drawall = Cvar_Get ("scr_drawall", "0", 0);
  423 
  424 //
  425 // register our commands
  426 //
  427         Cmd_AddCommand ("timerefresh",SCR_TimeRefresh_f);
  428         Cmd_AddCommand ("loading",SCR_Loading_f);
  429         Cmd_AddCommand ("sizeup",SCR_SizeUp_f);
  430         Cmd_AddCommand ("sizedown",SCR_SizeDown_f);
  431         Cmd_AddCommand ("sky",SCR_Sky_f);
  432 
  433         scr_initialized = true;
  434 }
  435 
  436 
  437 /*
  438 ==============
  439 SCR_DrawNet
  440 ==============
  441 */
  442 void SCR_DrawNet (void)
  443 {
  444         if (cls.netchan.outgoing_sequence - cls.netchan.incoming_acknowledged 
  445                 < CMD_BACKUP-1)
  446                 return;
  447 
  448         re.DrawPic (scr_vrect.x+64, scr_vrect.y, "net");
  449 }
  450 
  451 /*
  452 ==============
  453 SCR_DrawPause
  454 ==============
  455 */
  456 void SCR_DrawPause (void)
  457 {
  458         int             w, h;
  459 
  460         if (!scr_showpause->value)              // turn off for screenshots
  461                 return;
  462 
  463         if (!cl_paused->value)
  464                 return;
  465 
  466         re.DrawGetPicSize (&w, &h, "pause");
  467         re.DrawPic ((viddef.width-w)/2, viddef.height/2 + 8, "pause");
  468 }
  469 
  470 /*
  471 ==============
  472 SCR_DrawLoading
  473 ==============
  474 */
  475 void SCR_DrawLoading (void)
  476 {
  477         int             w, h;
  478                 
  479         if (!scr_draw_loading)
  480                 return;
  481 
  482         scr_draw_loading = false;
  483         re.DrawGetPicSize (&w, &h, "loading");
  484         re.DrawPic ((viddef.width-w)/2, (viddef.height-h)/2, "loading");
  485 }
  486 
  487 //=============================================================================
  488 
  489 /*
  490 ==================
  491 SCR_RunConsole
  492 
  493 Scroll it up or down
  494 ==================
  495 */
  496 void SCR_RunConsole (void)
  497 {
  498 // decide on the height of the console
  499         if (cls.key_dest == key_console)
  500                 scr_conlines = 0.5;             // half screen
  501         else
  502                 scr_conlines = 0;                               // none visible
  503         
  504         if (scr_conlines < scr_con_current)
  505         {
  506                 scr_con_current -= scr_conspeed->value*cls.frametime;
  507                 if (scr_conlines > scr_con_current)
  508                         scr_con_current = scr_conlines;
  509 
  510         }
  511         else if (scr_conlines > scr_con_current)
  512         {
  513                 scr_con_current += scr_conspeed->value*cls.frametime;
  514                 if (scr_conlines < scr_con_current)
  515                         scr_con_current = scr_conlines;
  516         }
  517 
  518 }
  519 
  520 /*
  521 ==================
  522 SCR_DrawConsole
  523 ==================
  524 */
  525 void SCR_DrawConsole (void)
  526 {
  527         Con_CheckResize ();
  528         
  529         if (cls.state == ca_disconnected || cls.state == ca_connecting)
  530         {       // forced full screen console
  531                 Con_DrawConsole (1.0);
  532                 return;
  533         }
  534 
  535         if (cls.state != ca_active || !cl.refresh_prepped)
  536         {       // connected, but can't render
  537                 Con_DrawConsole (0.5);
  538                 re.DrawFill (0, viddef.height/2, viddef.width, viddef.height/2, 0);
  539                 return;
  540         }
  541 
  542         if (scr_con_current)
  543         {
  544                 Con_DrawConsole (scr_con_current);
  545         }
  546         else
  547         {
  548                 if (cls.key_dest == key_game || cls.key_dest == key_message)
  549                         Con_DrawNotify ();      // only draw notify in game
  550         }
  551 }
  552 
  553 //=============================================================================
  554 
  555 /*
  556 ================
  557 SCR_BeginLoadingPlaque
  558 ================
  559 */
  560 void SCR_BeginLoadingPlaque (void)
  561 {
  562         S_StopAllSounds ();
  563         cl.sound_prepped = false;               // don't play ambients
  564         CDAudio_Stop ();
  565         if (cls.disable_screen)
  566                 return;
  567         if (developer->value)
  568                 return;
  569         if (cls.state == ca_disconnected)
  570                 return; // if at console, don't bring up the plaque
  571         if (cls.key_dest == key_console)
  572                 return;
  573         if (cl.cinematictime > 0)
  574                 scr_draw_loading = 2;   // clear to balack first
  575         else
  576                 scr_draw_loading = 1;
  577         SCR_UpdateScreen ();
  578         cls.disable_screen = Sys_Milliseconds ();
  579         cls.disable_servercount = cl.servercount;
  580 }
  581 
  582 /*
  583 ================
  584 SCR_EndLoadingPlaque
  585 ================
  586 */
  587 void SCR_EndLoadingPlaque (void)
  588 {
  589         cls.disable_screen = 0;
  590         Con_ClearNotify ();
  591 }
  592 
  593 /*
  594 ================
  595 SCR_Loading_f
  596 ================
  597 */
  598 void SCR_Loading_f (void)
  599 {
  600         SCR_BeginLoadingPlaque ();
  601 }
  602 
  603 /*
  604 ================
  605 SCR_TimeRefresh_f
  606 ================
  607 */
  608 int entitycmpfnc( const entity_t *a, const entity_t *b )
  609 {
  610         /*
  611         ** all other models are sorted by model then skin
  612         */
  613         if ( a->model == b->model )
  614         {
  615                 return ( ( int ) a->skin - ( int ) b->skin );
  616         }
  617         else
  618         {
  619                 return ( ( int ) a->model - ( int ) b->model );
  620         }
  621 }
  622 
  623 void SCR_TimeRefresh_f (void)
  624 {
  625         int             i;
  626         int             start, stop;
  627         float   time;
  628 
  629         if ( cls.state != ca_active )
  630                 return;
  631 
  632         start = Sys_Milliseconds ();
  633 
  634         if (Cmd_Argc() == 2)
  635         {       // run without page flipping
  636                 re.BeginFrame( 0 );
  637                 for (i=0 ; i<128 ; i++)
  638                 {
  639                         cl.refdef.viewangles[1] = i/128.0*360.0;
  640                         re.RenderFrame (&cl.refdef);
  641                 }
  642                 re.EndFrame();
  643         }
  644         else
  645         {
  646                 for (i=0 ; i<128 ; i++)
  647                 {
  648                         cl.refdef.viewangles[1] = i/128.0*360.0;
  649 
  650                         re.BeginFrame( 0 );
  651                         re.RenderFrame (&cl.refdef);
  652                         re.EndFrame();
  653                 }
  654         }
  655 
  656         stop = Sys_Milliseconds ();
  657         time = (stop-start)/1000.0;
  658         Com_Printf ("%f seconds (%f fps)\n", time, 128/time);
  659 }
  660 
  661 /*
  662 =================
  663 SCR_AddDirtyPoint
  664 =================
  665 */
  666 void SCR_AddDirtyPoint (int x, int y)
  667 {
  668         if (x < scr_dirty.x1)
  669                 scr_dirty.x1 = x;
  670         if (x > scr_dirty.x2)
  671                 scr_dirty.x2 = x;
  672         if (y < scr_dirty.y1)
  673                 scr_dirty.y1 = y;
  674         if (y > scr_dirty.y2)
  675                 scr_dirty.y2 = y;
  676 }
  677 
  678 void SCR_DirtyScreen (void)
  679 {
  680         SCR_AddDirtyPoint (0, 0);
  681         SCR_AddDirtyPoint (viddef.width-1, viddef.height-1);
  682 }
  683 
  684 /*
  685 ==============
  686 SCR_TileClear
  687 
  688 Clear any parts of the tiled background that were drawn on last frame
  689 ==============
  690 */
  691 void SCR_TileClear (void)
  692 {
  693         int             i;
  694         int             top, bottom, left, right;
  695         dirty_t clear;
  696 
  697         if (scr_drawall->value)
  698                 SCR_DirtyScreen ();     // for power vr or broken page flippers...
  699 
  700         if (scr_con_current == 1.0)
  701                 return;         // full screen console
  702         if (scr_viewsize->value == 100)
  703                 return;         // full screen rendering
  704         if (cl.cinematictime > 0)
  705                 return;         // full screen cinematic
  706 
  707         // erase rect will be the union of the past three frames
  708         // so tripple buffering works properly
  709         clear = scr_dirty;
  710         for (i=0 ; i<2 ; i++)
  711         {
  712                 if (scr_old_dirty[i].x1 < clear.x1)
  713                         clear.x1 = scr_old_dirty[i].x1;
  714                 if (scr_old_dirty[i].x2 > clear.x2)
  715                         clear.x2 = scr_old_dirty[i].x2;
  716                 if (scr_old_dirty[i].y1 < clear.y1)
  717                         clear.y1 = scr_old_dirty[i].y1;
  718                 if (scr_old_dirty[i].y2 > clear.y2)
  719                         clear.y2 = scr_old_dirty[i].y2;
  720         }
  721 
  722         scr_old_dirty[1] = scr_old_dirty[0];
  723         scr_old_dirty[0] = scr_dirty;
  724 
  725         scr_dirty.x1 = 9999;
  726         scr_dirty.x2 = -9999;
  727         scr_dirty.y1 = 9999;
  728         scr_dirty.y2 = -9999;
  729 
  730         // don't bother with anything convered by the console)
  731         top = scr_con_current*viddef.height;
  732         if (top >= clear.y1)
  733                 clear.y1 = top;
  734 
  735         if (clear.y2 <= clear.y1)
  736                 return;         // nothing disturbed
  737 
  738         top = scr_vrect.y;
  739         bottom = top + scr_vrect.height-1;
  740         left = scr_vrect.x;
  741         right = left + scr_vrect.width-1;
  742 
  743         if (clear.y1 < top)
  744         {       // clear above view screen
  745                 i = clear.y2 < top-1 ? clear.y2 : top-1;
  746                 re.DrawTileClear (clear.x1 , clear.y1,
  747                         clear.x2 - clear.x1 + 1, i - clear.y1+1, "backtile");
  748                 clear.y1 = top;
  749         }
  750         if (clear.y2 > bottom)
  751         {       // clear below view screen
  752                 i = clear.y1 > bottom+1 ? clear.y1 : bottom+1;
  753                 re.DrawTileClear (clear.x1, i,
  754                         clear.x2-clear.x1+1, clear.y2-i+1, "backtile");
  755                 clear.y2 = bottom;
  756         }
  757         if (clear.x1 < left)
  758         {       // clear left of view screen
  759                 i = clear.x2 < left-1 ? clear.x2 : left-1;
  760                 re.DrawTileClear (clear.x1, clear.y1,
  761                         i-clear.x1+1, clear.y2 - clear.y1 + 1, "backtile");
  762                 clear.x1 = left;
  763         }
  764         if (clear.x2 > right)
  765         {       // clear left of view screen
  766                 i = clear.x1 > right+1 ? clear.x1 : right+1;
  767                 re.DrawTileClear (i, clear.y1,
  768                         clear.x2-i+1, clear.y2 - clear.y1 + 1, "backtile");
  769                 clear.x2 = right;
  770         }
  771 
  772 }
  773 
  774 
  775 //===============================================================
  776 
  777 
  778 #define STAT_MINUS              10      // num frame for '-' stats digit
  779 char            *sb_nums[2][11] = 
  780 {
  781         {"num_0", "num_1", "num_2", "num_3", "num_4", "num_5",
  782         "num_6", "num_7", "num_8", "num_9", "num_minus"},
  783         {"anum_0", "anum_1", "anum_2", "anum_3", "anum_4", "anum_5",
  784         "anum_6", "anum_7", "anum_8", "anum_9", "anum_minus"}
  785 };
  786 
  787 #define ICON_WIDTH      24
  788 #define ICON_HEIGHT     24
  789 #define CHAR_WIDTH      16
  790 #define ICON_SPACE      8
  791 
  792 
  793 
  794 /*
  795 ================
  796 SizeHUDString
  797 
  798 Allow embedded \n in the string
  799 ================
  800 */
  801 void SizeHUDString (char *string, int *w, int *h)
  802 {
  803         int             lines, width, current;
  804 
  805         lines = 1;
  806         width = 0;
  807 
  808         current = 0;
  809         while (*string)
  810         {
  811                 if (*string == '\n')
  812                 {
  813                         lines++;
  814                         current = 0;
  815                 }
  816                 else
  817                 {
  818                         current++;
  819                         if (current > width)
  820                                 width = current;
  821                 }
  822                 string++;
  823         }
  824 
  825         *w = width * 8;
  826         *h = lines * 8;
  827 }
  828 
  829 void DrawHUDString (char *string, int x, int y, int centerwidth, int xor)
  830 {
  831         int             margin;
  832         char    line[1024];
  833         int             width;
  834         int             i;
  835 
  836         margin = x;
  837 
  838         while (*string)
  839         {
  840                 // scan out one line of text from the string
  841                 width = 0;
  842                 while (*string && *string != '\n')
  843                         line[width++] = *string++;
  844                 line[width] = 0;
  845 
  846                 if (centerwidth)
  847                         x = margin + (centerwidth - width*8)/2;
  848                 else
  849                         x = margin;
  850                 for (i=0 ; i<width ; i++)
  851                 {
  852                         re.DrawChar (x, y, line[i]^xor);
  853                         x += 8;
  854                 }
  855                 if (*string)
  856                 {
  857                         string++;       // skip the \n
  858                         x = margin;
  859                         y += 8;
  860                 }
  861         }
  862 }
  863 
  864 
  865 /*
  866 ==============
  867 SCR_DrawField
  868 ==============
  869 */
  870 void SCR_DrawField (int x, int y, int color, int width, int value)
  871 {
  872         char    num[16], *ptr;
  873         int             l;
  874         int             frame;
  875 
  876         if (width < 1)
  877                 return;
  878 
  879         // draw number string
  880         if (width > 5)
  881                 width = 5;
  882 
  883         SCR_AddDirtyPoint (x, y);
  884         SCR_AddDirtyPoint (x+width*CHAR_WIDTH+2, y+23);
  885 
  886         Com_sprintf (num, sizeof(num), "%i", value);
  887         l = strlen(num);
  888         if (l > width)
  889                 l = width;
  890         x += 2 + CHAR_WIDTH*(width - l);
  891 
  892         ptr = num;
  893         while (*ptr && l)
  894         {
  895                 if (*ptr == '-')
  896                         frame = STAT_MINUS;
  897                 else
  898                         frame = *ptr -'0';
  899 
  900                 re.DrawPic (x,y,sb_nums[color][frame]);
  901                 x += CHAR_WIDTH;
  902                 ptr++;
  903                 l--;
  904         }
  905 }
  906 
  907 
  908 /*
  909 ===============
  910 SCR_TouchPics
  911 
  912 Allows rendering code to cache all needed sbar graphics
  913 ===============
  914 */
  915 void SCR_TouchPics (void)
  916 {
  917         int             i, j;
  918 
  919         for (i=0 ; i<2 ; i++)
  920                 for (j=0 ; j<11 ; j++)
  921                         re.RegisterPic (sb_nums[i][j]);
  922 
  923         if (crosshair->value)
  924         {
  925                 if (crosshair->value > 3 || crosshair->value < 0)
  926                         crosshair->value = 3;
  927 
  928                 Com_sprintf (crosshair_pic, sizeof(crosshair_pic), "ch%i", (int)(crosshair->value));
  929                 re.DrawGetPicSize (&crosshair_width, &crosshair_height, crosshair_pic);
  930                 if (!crosshair_width)
  931                         crosshair_pic[0] = 0;
  932         }
  933 }
  934 
  935 /*
  936 ================
  937 SCR_ExecuteLayoutString 
  938 
  939 ================
  940 */
  941 void SCR_ExecuteLayoutString (char *s)
  942 {
  943         int             x, y;
  944         int             value;
  945         char    *token;
  946         int             width;
  947         int             index;
  948         clientinfo_t    *ci;
  949 
  950         if (cls.state != ca_active || !cl.refresh_prepped)
  951                 return;
  952 
  953         if (!s[0])
  954                 return;
  955 
  956         x = 0;
  957         y = 0;
  958         width = 3;
  959 
  960         while (s)
  961         {
  962                 token = COM_Parse (&s);
  963                 if (!strcmp(token, "xl"))
  964                 {
  965                         token = COM_Parse (&s);
  966                         x = atoi(token);
  967                         continue;
  968                 }
  969                 if (!strcmp(token, "xr"))
  970                 {
  971                         token = COM_Parse (&s);
  972                         x = viddef.width + atoi(token);
  973                         continue;
  974                 }
  975                 if (!strcmp(token, "xv"))
  976                 {
  977                         token = COM_Parse (&s);
  978                         x = viddef.width/2 - 160 + atoi(token);
  979                         continue;
  980                 }
  981 
  982                 if (!strcmp(token, "yt"))
  983                 {
  984                         token = COM_Parse (&s);
  985                         y = atoi(token);
  986                         continue;
  987                 }
  988                 if (!strcmp(token, "yb"))
  989                 {
  990                         token = COM_Parse (&s);
  991                         y = viddef.height + atoi(token);
  992                         continue;
  993                 }
  994                 if (!strcmp(token, "yv"))
  995                 {
  996                         token = COM_Parse (&s);
  997                         y = viddef.height/2 - 120 + atoi(token);
  998                         continue;
  999                 }
 1000 
 1001                 if (!strcmp(token, "pic"))
 1002                 {       // draw a pic from a stat number
 1003                         token = COM_Parse (&s);
 1004                         value = cl.frame.playerstate.stats[atoi(token)];
 1005                         if (value >= MAX_IMAGES)
 1006                                 Com_Error (ERR_DROP, "Pic >= MAX_IMAGES");
 1007                         if (cl.configstrings[CS_IMAGES+value])
 1008                         {
 1009                                 SCR_AddDirtyPoint (x, y);
 1010                                 SCR_AddDirtyPoint (x+23, y+23);
 1011                                 re.DrawPic (x, y, cl.configstrings[CS_IMAGES+value]);
 1012                         }
 1013                         continue;
 1014                 }
 1015 
 1016                 if (!strcmp(token, "client"))
 1017                 {       // draw a deathmatch client block
 1018                         int             score, ping, time;
 1019 
 1020                         token = COM_Parse (&s);
 1021                         x = viddef.width/2 - 160 + atoi(token);
 1022                         token = COM_Parse (&s);
 1023                         y = viddef.height/2 - 120 + atoi(token);
 1024                         SCR_AddDirtyPoint (x, y);
 1025                         SCR_AddDirtyPoint (x+159, y+31);
 1026 
 1027                         token = COM_Parse (&s);
 1028                         value = atoi(token);
 1029                         if (value >= MAX_CLIENTS || value < 0)
 1030                                 Com_Error (ERR_DROP, "client >= MAX_CLIENTS");
 1031                         ci = &cl.clientinfo[value];
 1032 
 1033                         token = COM_Parse (&s);
 1034                         score = atoi(token);
 1035 
 1036                         token = COM_Parse (&s);
 1037                         ping = atoi(token);
 1038 
 1039                         token = COM_Parse (&s);
 1040                         time = atoi(token);
 1041 
 1042                         DrawAltString (x+32, y, ci->name);
 1043                         DrawString (x+32, y+8,  "Score: ");
 1044                         DrawAltString (x+32+7*8, y+8,  va("%i", score));
 1045                         DrawString (x+32, y+16, va("Ping:  %i", ping));
 1046                         DrawString (x+32, y+24, va("Time:  %i", time));
 1047 
 1048                         if (!ci->icon)
 1049                                 ci = &cl.baseclientinfo;
 1050                         re.DrawPic (x, y, ci->iconname);
 1051                         continue;
 1052                 }
 1053 
 1054                 if (!strcmp(token, "ctf"))
 1055                 {       // draw a ctf client block
 1056                         int             score, ping;
 1057                         char    block[80];
 1058 
 1059                         token = COM_Parse (&s);
 1060                         x = viddef.width/2 - 160 + atoi(token);
 1061                         token = COM_Parse (&s);
 1062                         y = viddef.height/2 - 120 + atoi(token);
 1063                         SCR_AddDirtyPoint (x, y);
 1064                         SCR_AddDirtyPoint (x+159, y+31);
 1065 
 1066                         token = COM_Parse (&s);
 1067                         value = atoi(token);
 1068                         if (value >= MAX_CLIENTS || value < 0)
 1069                                 Com_Error (ERR_DROP, "client >= MAX_CLIENTS");
 1070                         ci = &cl.clientinfo[value];
 1071 
 1072                         token = COM_Parse (&s);
 1073                         score = atoi(token);
 1074 
 1075                         token = COM_Parse (&s);
 1076                         ping = atoi(token);
 1077                         if (ping > 999)
 1078                                 ping = 999;
 1079 
 1080                         sprintf(block, "%3d %3d %-12.12s", score, ping, ci->name);
 1081 
 1082                         if (value == cl.playernum)
 1083                                 DrawAltString (x, y, block);
 1084                         else
 1085                                 DrawString (x, y, block);
 1086                         continue;
 1087                 }
 1088 
 1089                 if (!strcmp(token, "picn"))
 1090                 {       // draw a pic from a name
 1091                         token = COM_Parse (&s);
 1092                         SCR_AddDirtyPoint (x, y);
 1093                         SCR_AddDirtyPoint (x+23, y+23);
 1094                         re.DrawPic (x, y, token);
 1095                         continue;
 1096                 }
 1097 
 1098                 if (!strcmp(token, "num"))
 1099                 {       // draw a number
 1100                         token = COM_Parse (&s);
 1101                         width = atoi(token);
 1102                         token = COM_Parse (&s);
 1103                         value = cl.frame.playerstate.stats[atoi(token)];
 1104                         SCR_DrawField (x, y, 0, width, value);
 1105                         continue;
 1106                 }
 1107 
 1108                 if (!strcmp(token, "hnum"))
 1109                 {       // health number
 1110                         int             color;
 1111 
 1112                         width = 3;
 1113                         value = cl.frame.playerstate.stats[STAT_HEALTH];
 1114                         if (value > 25)
 1115                                 color = 0;      // green
 1116                         else if (value > 0)
 1117                                 color = (cl.frame.serverframe>>2) & 1;          // flash
 1118                         else
 1119                                 color = 1;
 1120 
 1121                         if (cl.frame.playerstate.stats[STAT_FLASHES] & 1)
 1122                                 re.DrawPic (x, y, "field_3");
 1123 
 1124                         SCR_DrawField (x, y, color, width, value);
 1125                         continue;
 1126                 }
 1127 
 1128                 if (!strcmp(token, "anum"))
 1129                 {       // ammo number
 1130                         int             color;
 1131 
 1132                         width = 3;
 1133                         value = cl.frame.playerstate.stats[STAT_AMMO];
 1134                         if (value > 5)
 1135                                 color = 0;      // green
 1136                         else if (value >= 0)
 1137                                 color = (cl.frame.serverframe>>2) & 1;          // flash
 1138                         else
 1139                                 continue;       // negative number = don't show
 1140 
 1141                         if (cl.frame.playerstate.stats[STAT_FLASHES] & 4)
 1142                                 re.DrawPic (x, y, "field_3");
 1143 
 1144                         SCR_DrawField (x, y, color, width, value);
 1145                         continue;
 1146                 }
 1147 
 1148                 if (!strcmp(token, "rnum"))
 1149                 {       // armor number
 1150                         int             color;
 1151 
 1152                         width = 3;
 1153                         value = cl.frame.playerstate.stats[STAT_ARMOR];
 1154                         if (value < 1)
 1155                                 continue;
 1156 
 1157                         color = 0;      // green
 1158 
 1159                         if (cl.frame.playerstate.stats[STAT_FLASHES] & 2)
 1160                                 re.DrawPic (x, y, "field_3");
 1161 
 1162                         SCR_DrawField (x, y, color, width, value);
 1163                         continue;
 1164                 }
 1165 
 1166 
 1167                 if (!strcmp(token, "stat_string"))
 1168                 {
 1169                         token = COM_Parse (&s);
 1170                         index = atoi(token);
 1171                         if (index < 0 || index >= MAX_CONFIGSTRINGS)
 1172                                 Com_Error (ERR_DROP, "Bad stat_string index");
 1173                         index = cl.frame.playerstate.stats[index];
 1174                         if (index < 0 || index >= MAX_CONFIGSTRINGS)
 1175                                 Com_Error (ERR_DROP, "Bad stat_string index");
 1176                         DrawString (x, y, cl.configstrings[index]);
 1177                         continue;
 1178                 }
 1179 
 1180                 if (!strcmp(token, "cstring"))
 1181                 {
 1182                         token = COM_Parse (&s);
 1183                         DrawHUDString (token, x, y, 320, 0);
 1184                         continue;
 1185                 }
 1186 
 1187                 if (!strcmp(token, "string"))
 1188                 {
 1189                         token = COM_Parse (&s);
 1190                         DrawString (x, y, token);
 1191                         continue;
 1192                 }
 1193 
 1194                 if (!strcmp(token, "cstring2"))
 1195                 {
 1196                         token = COM_Parse (&s);
 1197                         DrawHUDString (token, x, y, 320,0x80);
 1198                         continue;
 1199                 }
 1200 
 1201                 if (!strcmp(token, "string2"))
 1202                 {
 1203                         token = COM_Parse (&s);
 1204                         DrawAltString (x, y, token);
 1205                         continue;
 1206                 }
 1207 
 1208                 if (!strcmp(token, "if"))
 1209                 {       // draw a number
 1210                         token = COM_Parse (&s);
 1211                         value = cl.frame.playerstate.stats[atoi(token)];
 1212                         if (!value)
 1213                         {       // skip to endif
 1214                                 while (s && strcmp(token, "endif") )
 1215                                 {
 1216                                         token = COM_Parse (&s);
 1217                                 }
 1218                         }
 1219 
 1220                         continue;
 1221                 }
 1222 
 1223 
 1224         }
 1225 }
 1226 
 1227 
 1228 /*
 1229 ================
 1230 SCR_DrawStats
 1231 
 1232 The status bar is a small layout program that
 1233 is based on the stats array
 1234 ================
 1235 */
 1236 void SCR_DrawStats (void)
 1237 {
 1238         SCR_ExecuteLayoutString (cl.configstrings[CS_STATUSBAR]);
 1239 }
 1240 
 1241 
 1242 /*
 1243 ================
 1244 SCR_DrawLayout
 1245 
 1246 ================
 1247 */
 1248 #define STAT_LAYOUTS            13
 1249 
 1250 void SCR_DrawLayout (void)
 1251 {
 1252         if (!cl.frame.playerstate.stats[STAT_LAYOUTS])
 1253                 return;
 1254         SCR_ExecuteLayoutString (cl.layout);
 1255 }
 1256 
 1257 //=======================================================
 1258 
 1259 /*
 1260 ==================
 1261 SCR_UpdateScreen
 1262 
 1263 This is called every frame, and can also be called explicitly to flush
 1264 text to the screen.
 1265 ==================
 1266 */
 1267 void SCR_UpdateScreen (void)
 1268 {
 1269         int numframes;
 1270         int i;
 1271         float separation[2] = { 0, 0 };
 1272 
 1273         // if the screen is disabled (loading plaque is up, or vid mode changing)
 1274         // do nothing at all
 1275         if (cls.disable_screen)
 1276         {
 1277                 if (Sys_Milliseconds() - cls.disable_screen > 120000)
 1278                 {
 1279                         cls.disable_screen = 0;
 1280                         Com_Printf ("Loading plaque timed out.\n");
 1281                 }
 1282                 return;
 1283         }
 1284 
 1285         if (!scr_initialized || !con.initialized)
 1286                 return;                         // not initialized yet
 1287 
 1288         /*
 1289         ** range check cl_camera_separation so we don't inadvertently fry someone's
 1290         ** brain
 1291         */
 1292         if ( cl_stereo_separation->value > 1.0 )
 1293                 Cvar_SetValue( "cl_stereo_separation", 1.0 );
 1294         else if ( cl_stereo_separation->value < 0 )
 1295                 Cvar_SetValue( "cl_stereo_separation", 0.0 );
 1296 
 1297         if ( cl_stereo->value )
 1298         {
 1299                 numframes = 2;
 1300                 separation[0] = -cl_stereo_separation->value / 2;
 1301                 separation[1] =  cl_stereo_separation->value / 2;
 1302         }               
 1303         else
 1304         {
 1305                 separation[0] = 0;
 1306                 separation[1] = 0;
 1307                 numframes = 1;
 1308         }
 1309 
 1310         for ( i = 0; i < numframes; i++ )
 1311         {
 1312                 re.BeginFrame( separation[i] );
 1313 
 1314                 if (scr_draw_loading == 2)
 1315                 {       //  loading plaque over black screen
 1316                         int             w, h;
 1317 
 1318                         re.CinematicSetPalette(NULL);
 1319                         scr_draw_loading = false;
 1320                         re.DrawGetPicSize (&w, &h, "loading");
 1321                         re.DrawPic ((viddef.width-w)/2, (viddef.height-h)/2, "loading");
 1322 //                      re.EndFrame();
 1323 //                      return;
 1324                 } 
 1325                 // if a cinematic is supposed to be running, handle menus
 1326                 // and console specially
 1327                 else if (cl.cinematictime > 0)
 1328                 {
 1329                         if (cls.key_dest == key_menu)
 1330                         {
 1331                                 if (cl.cinematicpalette_active)
 1332                                 {
 1333                                         re.CinematicSetPalette(NULL);
 1334                                         cl.cinematicpalette_active = false;
 1335                                 }
 1336                                 M_Draw ();
 1337 //                              re.EndFrame();
 1338 //                              return;
 1339                         }
 1340                         else if (cls.key_dest == key_console)
 1341                         {
 1342                                 if (cl.cinematicpalette_active)
 1343                                 {
 1344                                         re.CinematicSetPalette(NULL);
 1345                                         cl.cinematicpalette_active = false;
 1346                                 }
 1347                                 SCR_DrawConsole ();
 1348 //                              re.EndFrame();
 1349 //                              return;
 1350                         }
 1351                         else
 1352                         {
 1353                                 SCR_DrawCinematic();
 1354 //                              re.EndFrame();
 1355 //                              return;
 1356                         }
 1357                 }
 1358                 else 
 1359                 {
 1360 
 1361                         // make sure the game palette is active
 1362                         if (cl.cinematicpalette_active)
 1363                         {
 1364                                 re.CinematicSetPalette(NULL);
 1365                                 cl.cinematicpalette_active = false;
 1366                         }
 1367 
 1368                         // do 3D refresh drawing, and then update the screen
 1369                         SCR_CalcVrect ();
 1370 
 1371                         // clear any dirty part of the background
 1372                         SCR_TileClear ();
 1373 
 1374                         V_RenderView ( separation[i] );
 1375 
 1376                         SCR_DrawStats ();
 1377                         if (cl.frame.playerstate.stats[STAT_LAYOUTS] & 1)
 1378                                 SCR_DrawLayout ();
 1379                         if (cl.frame.playerstate.stats[STAT_LAYOUTS] & 2)
 1380                                 CL_DrawInventory ();
 1381 
 1382                         SCR_DrawNet ();
 1383                         SCR_CheckDrawCenterString ();
 1384 
 1385                         if (scr_timegraph->value)
 1386                                 SCR_DebugGraph (cls.frametime*300, 0);
 1387 
 1388                         if (scr_debuggraph->value || scr_timegraph->value || scr_netgraph->value)
 1389                                 SCR_DrawDebugGraph ();
 1390 
 1391                         SCR_DrawPause ();
 1392 
 1393                         SCR_DrawConsole ();
 1394 
 1395                         M_Draw ();
 1396 
 1397                         SCR_DrawLoading ();
 1398                 }
 1399         }
 1400         re.EndFrame();
 1401 }
 1402