_files/image002.jpg)
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
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, |