Quake DeveLS - Statusbar

Author: Decker
Difficulty:
Hard

This tutorial will help you understand the statusbar layout and control, in three simple steps:

- Statusbar macrolanguage [file: g_spawn.c]
- STAT_* values [file: q_shared.h]
- G_SetStats() [file: p_hud.c]

There are two different types of statusbars; one for singleplayer and one for deathmatch. The layout of both are described by a very simple macrolanguage.

First take a look at the statusbar macrolanguage, which is located in the file g_spawn.c at line 590. I've added some comments that isn't in the original code, and don't be alarmed if there are some words that you don't understand, they will be explained later:

 
 
//===================================================================
 
#if 0
  // cursor positioning
  xl             // X position from the left side of the physical screen
  xr             // X position from the right side of the physical screen
  yb             // Y position from the bottom of the physical screen
  yt             // Y position from the top of the physical screen
  xv             // X position on the virtual screen (virtual screensize is 320x200)
  yv             // Y position on the virtual screen (virtual screensize is 320x200)
 
  // drawing
  statpic                // Unknown
  pic            // Icon field, imageindex in stat-array at index 
  num     // Number field, value in stat-array at index 
  string         // Unknown
 
// DECKER: This isn't in the original source, but I've inserted it for completeness
  stat_string    // String field, display pickup-string from itemlist-array at 
                         // the index that the value in stat-array that index  points to.
                         // (I'll explain later!)
 
  // control
  if             // Statement is true if content of  is not zero
  ifeq    // Statement is true if content of  is equal to 
  ifbit   // Unknown (Maybe an OR-expression)
  endif       
#endif
 
char *single_statusbar =        // Variable that contains statusbar for singleplayer
                         // The macrolanguage is in one big text-variable
                         
"yb      -24 "                  // Set y-cursor to physical screen bottom minus 24 pixels
                         // (Default icon-size is 24*24 pixels, but I guess it could vary)
 
// health
"xv      0 "                    // Set x-cursor to virtual screen (0 pixels from left)
"hnum "                         // Health number (3 digits) (controlled internally)
"xv      50 "                   // Set x-cursor to virtual screen (50 pixels from left)
"pic     0 "                    // Show icon where imageindex is at index 0 in the stat-array (Health-icon)
 
// ammo
"if 2 "                         // If value at index 2 in the stat-array is not zero, then do
" xv      100 "          // Set x-cursor to virtual screen (100 pixels from left)
" anum "                 // Ammo number (3 digits) (controlled internally)
" xv      150 "          // Set x-cursor to virtual screen (150 pixels from left)
" pic     2 "            // Show icon where imageindex is at index 2 in the stat-array (Ammo-icons)
"endif "                 // End-statement
 
         // DECKER:
         // The above will say that, if there is an icon (imageindex greater than zero)
         // in the stat-array at index 2, then show ammount of ammo and the respective
         // icon for that ammo-type.
 
// armor
"if 4 "                         // As above
" xv      200 "
" rnum "
" xv      250 "
" pic     4 "
"endif "
 
// selected item
"if 6 "                         // As above
" xv      296 "
" pic 6 "
"endif "
 
"yb      -50 "                  // Set y-cursor to physical screen bottom minus 50 pixels
 
// picked up item
"if 7 "                         // As above
" xv      0 "
" pic 7 "
" xv      26 "
" yb      -42 "          // Set y-cursor to physical screen bottom minus 42 pixels
" stat_string 8 "        // Display pickup-string from itemlist-array at the index that
                         // the value in stat-array at index 8 points to. (Got that?)
" yb      -50 "          // Restore y-cursor to previous value
"endif "
 
// timer
"if 9 "                         // As above
" xv      262 "
" num     2       10 "    // Display a two-digit value that is contained in stat-array at index 10
" xv      296 "
" pic     9 "
"endif "
 
//  help / weapon icon 
"if 11 "                 // As above
" xv      148 "
" pic     11 "
"endif "
;
 
char *dm_statusbar =            // Variable that contains statusbar for deathmatch
                         // The deathmatch-statusbar only adds a frags-counter, 
 
"yb      -24 "
 
// health
"xv      0 "
"hnum "
"xv      50 "
"pic 0 "
 
// ammo
"if 2 "
" xv      100 "
" anum "
" xv      150 "
" pic 2 "
"endif "
 
// armor
"if 4 "
" xv      200 "
" rnum "
" xv      250 "
" pic 4 "
"endif "
 
// selected item
"if 6 "
" xv      296 "
" pic 6 "
"endif "
 
"yb      -50 "
 
// picked up item
"if 7 "
" xv      0 "
" pic 7 "
" xv      26 "
" yb      -42 "
" stat_string 8 "
" yb      -50 "
"endif "
 
// timer
"if 9 "
" xv      246 "
" num     2       10 "
" xv      296 "
" pic     9 "
"endif "
 
//  help / weapon icon 
"if 11 "
" xv      148 "
" pic     11 "
"endif "
 
//  frags
"xr      -50 "                  // Set x-cursor to physical screen -50 pixels from the right
"yt 2 "                         // Set y-cursor to physical screen 2 pixels from the top
"num 3 14"                      // Display a three-digit value that is contained in stat-array at index 14
;
 
//===================================================================
 

Phew! Now did you understand just a little of that, that's good. If not, please read it over again.

Word of advise: The macros "hnum", "anum" and "rnum" are controlled elsewhere, they are not like the "num " which can be customized by the DLL.

So much for the macrolanguage, it alone controls _where_ numbers and icons should be displayed at the screen. Together with some real code, it also controls _when_ they are displayed, but more about that later.

Lets view the STAT_* constants in the file q_shared.h:

 
 
//===================================================================
 
// player_state->stats[] indexes
#define STAT_HEALTH_ICON 0
#define  STAT_HEALTH            1
#define  STAT_AMMO_ICON         2
#define  STAT_AMMO              3
#define  STAT_ARMOR_ICON        4
#define  STAT_ARMOR             5
#define  STAT_SELECTED_ICON     6
#define  STAT_PICKUP_ICON       7
#define  STAT_PICKUP_STRING     8
#define  STAT_TIMER_ICON        9
#define  STAT_TIMER             10
#define  STAT_HELPICON          11
#define  STAT_SELECTED_ITEM     12
#define  STAT_LAYOUTS           13
#define  STAT_FRAGS             14
#define  STAT_FLASHES           15      // cleared each frame, 1 = health, 2 = armor
 
#define  MAX_STATS              32
 
//===================================================================
 

As one can tell, there are already used 16 status-objects, but room for 16 more (16-31). You probably shouldn't try to modify any of these, because some of them are hardcoded into Quake2's kernel, not the DLL. (Hint: Try to find out if STAT_LAYOUTS are used anywhere else that in p_hud.c (I still have to figure out what it does))

These values are directly referreing to an array each player-entity has; status-array or stats[]. It is defined in file: q_shared.h in the typedef struct player_state_t (at the very bottom of the file).

Take a look at the STAT_PICKUP_ICON. It's value is 7. Now take a look at the macro:

 
 
// picked up item
"if 7 "                  <-- Wohoo we got a match!
" xv      0 "
" pic 7 "        <-- And even one more that's matching!
" xv      26 "
" yb      -42 "
" stat_string 8 "
" yb      -50 "
"endif "
 

Also notice the value that STAT_PICKUP_STRING has. Can you find it in the macro?

Okay, now that we can make relations between the macrolanguage and the STAT_* constants, we move over to find out how all this is controlled, and what those stat-array, imageindex, stat_string and values are.

Look at the function G_SetStats in the file p_hud.c, which I've commented here and there:

 
 
//===================================================================
 
void G_SetStats (edict_t *ent)
{
  gitem_t        *item;
  int            index, cells;
  int            power_armor_type;
 
  //
  // health
  //
  ent->client->ps.stats[STAT_HEALTH_ICON] = level.pic_health;
  ent->client->ps.stats[STAT_HEALTH] = ent->health;
 
// DECKER: Now the health-icon and health have been set for this player
 
  //
  // ammo
  //
  if (!ent->client->ammo_index /* || !ent->client->pers.inventory[ent->client->ammo_index] */)
  {
         ent->client->ps.stats[STAT_AMMO_ICON] = 0;
         ent->client->ps.stats[STAT_AMMO] = 0;
         
// DECKER: If the player is holding a weapon that does not require any ammo, the ammo-icon and 
// ammo-ammount are cleared, thus not displaying any icons or numbers. (Remember the ammo-macro's
// if-structure)
         
  }
  else
  {
         item = &itemlist[ent->client->ammo_index];
         ent->client->ps.stats[STAT_AMMO_ICON] = gi.imageindex (item->icon);
         ent->client->ps.stats[STAT_AMMO] = ent->client->pers.inventory[ent->client->ammo_index];
         
// DECKER: Otherwise, we find the item-number of the ammo-type (ammo_index), and from that the 
// imageindex of the item-icon. We also remember to set the ammo-ammount.
 
  }
  
  //
  // armor
  //
  power_armor_type = PowerArmorType (ent);
  if (power_armor_type)
  {
         cells = ent->client->pers.inventory[ITEM_INDEX(FindItem ("cells"))];
         if (cells == 0)
         {       // ran out of cells for power armor
                 ent->flags &= ~FL_POWER_ARMOR;
                 gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/power1.wav"), 1, ATTN_NORM, 0);    //FIXME powering down sound
                 power_armor_type = 0;;
         }
  }
 
// DECKER: Above does some stuff that isn't directly related to the statusbar, other than to 
// check if power_armor ran out of cells.
 
  index = ArmorIndex (ent);
  if (power_armor_type && (!index || (level.framenum & 8) ) )
  {       // flash between power armor and other armor icon
         ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex ("i_powershield");
         ent->client->ps.stats[STAT_ARMOR] = cells;
  }
  else if (index)
  {
         item = GetItemByIndex (index);
         ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex (item->icon);
         ent->client->ps.stats[STAT_ARMOR] = ent->client->pers.inventory[index];
  }
  else
  {
         ent->client->ps.stats[STAT_ARMOR_ICON] = 0;
         ent->client->ps.stats[STAT_ARMOR] = 0;
  }
 
// DECKER: The above controls the armor and power_armor icons and values.
 
  //
  // pickup message
  //
  if (level.time > ent->client->pickup_msg_time)
  {
         ent->client->ps.stats[STAT_PICKUP_ICON] = 0;
         ent->client->ps.stats[STAT_PICKUP_STRING] = 0;
  }
 
// DECKER: Clears the pickup message and icon when time is up. It is set in file g_items.c
 
  //
  // timers
  //
  if (ent->client->quad_framenum > level.framenum)
  {
         ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_quad");
         ent->client->ps.stats[STAT_TIMER] = (ent->client->quad_framenum - level.framenum)/10;
  }
  else if (ent->client->invincible_framenum > level.framenum)
  {
         ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_invulnerability");
         ent->client->ps.stats[STAT_TIMER] = (ent->client->invincible_framenum - level.framenum)/10;
  }
  else if (ent->client->enviro_framenum > level.framenum)
  {
         ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_envirosuit");
         ent->client->ps.stats[STAT_TIMER] = (ent->client->enviro_framenum - level.framenum)/10;
  }
  else if (ent->client->breather_framenum > level.framenum)
  {
         ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_rebreather");
         ent->client->ps.stats[STAT_TIMER] = (ent->client->breather_framenum - level.framenum)/10;
  }
  else
  {
         ent->client->ps.stats[STAT_TIMER_ICON] = 0;
         ent->client->ps.stats[STAT_TIMER] = 0;
  }
 
// DECKER: Above controls all our powerups; Quad, Invulnerability, Envirosuit and Rebreather, but
// only one will be shown, even if all are active, because they are using the same index in the
// stat-array. We'll change that later in this tutorial!
 
  //
  // selected item
  //
  if (ent->client->pers.selected_item == -1)
         ent->client->ps.stats[STAT_SELECTED_ICON] = 0;
  else
         ent->client->ps.stats[STAT_SELECTED_ICON] = gi.imageindex (itemlist[ent->client->pers.selected_item].icon);
 
// DECKER: Shows what item in the players inventory that is currently selected (if any)
 
  ent->client->ps.stats[STAT_SELECTED_ITEM] = ent->client->pers.selected_item;
 
// DECKER: Now this is not refered in any of the statusbar-macros, so what it is suppose to do, who knows...
 
  //
  // layouts
  //
  ent->client->ps.stats[STAT_LAYOUTS] = 0;
 
  if (deathmatch->value)
  {
         if (ent->client->pers.health <= 0 || level.intermissiontime
                 || ent->client->showscores)
                 ent->client->ps.stats[STAT_LAYOUTS] |= 1;
         if (ent->client->showinventory && ent->client->pers.health > 0)
                 ent->client->ps.stats[STAT_LAYOUTS] |= 2;
  }
  else
  {
         if (ent->client->showscores)
                 ent->client->ps.stats[STAT_LAYOUTS] |= 1;
         if (ent->client->showinventory && ent->client->pers.health > 0)
                 ent->client->ps.stats[STAT_LAYOUTS] |= 2;
  }
 
// DECKER: Beats me. I can't see any change in the statusbar when any of the conditions are met.
 
  //
  // frags
  //
  ent->client->ps.stats[STAT_FRAGS] = ent->client->resp.score;
 
// DECKER: Oh yeah! Get me some more frags! >-] 
// Notice that there isn't any 'is deathmatch active' checks here, because if you run single-
// player, the frags won't show on your statusbar as the statusbar-macro does not refer it.
 
  //
  // help icon
  //
  if (game.helpchanged && (level.framenum & 8) )
         ent->client->ps.stats[STAT_HELPICON] = gi.imageindex ("i_help");
  else if (ent->client->pers.hand == CENTER_HANDED && ent->client->pers.weapon)
         ent->client->ps.stats[STAT_HELPICON] = gi.imageindex (ent->client->pers.weapon->icon);
  else
         ent->client->ps.stats[STAT_HELPICON] = 0;
 
// DECKER: Above controls the show/don't show of the help-icon and if the player set his "HAND 2"
// it displays the icon of the currently selected weapon, by finding the imageindex of it.
  
}
 
//===================================================================
 

MODIFYING

Now lets do something usefull (well almost). Let us display all powerups when they are active at the same time.

First we will need to have 3 more icons and values displayed on screen, and just for fun, we'll put them to the right bottom, instead of just above the 'selected item'-icon.

Add these lines to the other STAT_* constants in q_shared.h:

 
 
#define  STAT_TIMER2_ICON       16
#define  STAT_TIMER2            17
#define  STAT_TIMER3_ICON       18
#define  STAT_TIMER3            19
#define  STAT_TIMER4_ICON       20
#define  STAT_TIMER4            21
 

To show these 3 new 'timers', we have to modify the statusbar-macro, so it knows when and where to display them.

WARNING: Be aware that there should always be atleast one space between words in the macro! Be extra carefull that all lines end with an space!

In the file g_spawn.c find the *single_statusbar variable, and change it into:

 
 
char *single_statusbar = 
"yb      -24 "
 
// health
"xv      0 "
"hnum "
"xv      50 "
"pic 0 "
 
// ammo
"if 2 "
" xv      100 "
" anum "
" xv      150 "
" pic 2 "
"endif "
 
// armor
"if 4 "
" xv      200 "
" rnum "
" xv      250 "
" pic 4 "
"endif "
 
// selected item
"if 6 "
" xv      296 "
" pic 6 "
"endif "
 
"yb      -50 "
 
// picked up item
"if 7 "
" xv      0 "
" pic 7 "
" xv      26 "
" yb      -42 "
" stat_string 8 "
" yb      -50 "
"endif "
 
// timer
"if 9 "
" yb      -24 "          // New. Set Y-cursor -24 pixels from physical screen bottom
" xr      -58 "          // New. Set X-cursor -58 pixels from physical screen right
" num     2       10 "
" xr      -24 "          // New
" pic     9 "
"endif "
        
// timer2                // New
"if 16 "                 // New. If STAT_TIMER2_ICON is not zero, then do
" yb      -48 "          // New
" xr      -58 "          // New
" num     2       17 "    // New. Display 2-digits with value from stat-array at index 17
" xr      -24 "          // New
" pic     16 "           // New. Display icon
"endif "                 // New
        
// timer3                // New
"if 18 "                 // New. If STAT_TIMER3_ICON is not zero, then do
" yb      -72 "          // New
" xr      -58 "          // New
" num     2       19 "    // New. Display 2-digits with value from stat-array at index 19
" xr      -24 "          // New
" pic     18 "           // New. Display icon
"endif "                 // New
 
// timer4                // New
"if 20 "                 // New. If STAT_TIMER4_ICON is not zero, then do
" yb      -96 "          // New
" xr      -58 "          // New
" num     2       21 "    // New. Display 2-digits with value from stat-array at index 21
" xr      -24 "          // New
" pic     20 "           // New. Display icon
"endif "                 // New
        
//  help / weapon icon 
"if 11 "
" xv      148 "
" pic     11 "
"endif "
;
 

Now that the statusbar-layout have been done, lets move on to the control. In the file p_hud.c find the G_SetStats function, and change the timers to this:

 
 
// [...do not touch above code...]
 
  //
  // timers
  //
  if (ent->client->quad_framenum > level.framenum)
  {
         ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_quad");
         ent->client->ps.stats[STAT_TIMER] = (ent->client->quad_framenum - level.framenum)/10;
  }
  else                                                  // New
  {                                                     // New
         ent->client->ps.stats[STAT_TIMER_ICON] = 0;   // New
         ent->client->ps.stats[STAT_TIMER] = 0;          // New
  }                                                     // New
  
  if (ent->client->invincible_framenum > level.framenum)
  {
         ent->client->ps.stats[STAT_TIMER2_ICON] = gi.imageindex ("p_invulnerability");
         ent->client->ps.stats[STAT_TIMER2] = (ent->client->invincible_framenum - level.framenum)/10;
  }
  else                                                  // New
  {                                                     // New
         ent->client->ps.stats[STAT_TIMER2_ICON] = 0;  // New
         ent->client->ps.stats[STAT_TIMER2] = 0;         // New
  }                                                     // New
  
  if (ent->client->enviro_framenum > level.framenum)
  {
         ent->client->ps.stats[STAT_TIMER3_ICON] = gi.imageindex ("p_envirosuit");
         ent->client->ps.stats[STAT_TIMER3] = (ent->client->enviro_framenum - level.framenum)/10;
  }
  else                                                  // New
  {                                                     // New
         ent->client->ps.stats[STAT_TIMER3_ICON] = 0;  // New
         ent->client->ps.stats[STAT_TIMER3] = 0;         // New
  }                                                     // New
  
  if (ent->client->breather_framenum > level.framenum)
  {
         ent->client->ps.stats[STAT_TIMER4_ICON] = gi.imageindex ("p_rebreather");
         ent->client->ps.stats[STAT_TIMER4] = (ent->client->breather_framenum - level.framenum)/10;
  }
  else                                                  // New
  {                                                     // New
         ent->client->ps.stats[STAT_TIMER4_ICON] = 0;  // New
         ent->client->ps.stats[STAT_TIMER4] = 0;         // New
  }                                                     // New
 
  //
  // selected item
  //
 
// [...do not touch below code...]
 

Compile, link, copy the DLL, and run Quake2. Do a GIVE ALL (Yeah cheat is great when developing) and try activating the powerups.

If you have comments, hints, explanations or other stuff regarding Quake2-DLL coding, you are welcome to write.

Tutorial by Decker

 
 
 

This site, and all content and graphics displayed on it,
are ©opyrighted to the Quake DeveLS team. All rights received.
Got a suggestion? Comment? Question? Hate mail? Send it to us!
Oh yeah, this site is best viewed in 16 Bit or higher, with the resolution on 800*600.
Thanks to Planet Quake for there great help and support with hosting.
Best viewed with Netscape 4 or IE 3

 

--1049889F-WebSite-Rules-Byte-Range-Data-1049889F--