]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/client/hud.qc
Merge branch 'master' into Mario/buffs
[xonotic/xonotic-data.pk3dir.git] / qcsrc / client / hud.qc
index 3d079bc8ad5caa700189b1834c538605dac192d0..e1c95025892e272c556f33edbc86275df630a21a 100644 (file)
@@ -142,16 +142,16 @@ float stringwidth_nocolors(string s, vector theSize)
        return stringwidth(s, FALSE, theSize);
 }
 
-void drawstringright(vector position, string text, vector scale, vector rgb, float theAlpha, float flag)
+void drawstringright(vector position, string text, vector theScale, vector rgb, float theAlpha, float flag)
 {
-       position_x -= 2 / 3 * strlen(text) * scale_x;
-       drawstring(position, text, scale, rgb, theAlpha, flag);
+       position_x -= 2 / 3 * strlen(text) * theScale_x;
+       drawstring(position, text, theScale, rgb, theAlpha, flag);
 }
 
-void drawstringcenter(vector position, string text, vector scale, vector rgb, float theAlpha, float flag)
+void drawstringcenter(vector position, string text, vector theScale, vector rgb, float theAlpha, float flag)
 {
-       position_x = 0.5 * (vid_conwidth - 0.6025 * strlen(text) * scale_x);
-       drawstring(position, text, scale, rgb, theAlpha, flag);
+       position_x = 0.5 * (vid_conwidth - 0.6025 * strlen(text) * theScale_x);
+       drawstring(position, text, theScale, rgb, theAlpha, flag);
 }
 
 // return the string of the onscreen race timer
@@ -242,7 +242,7 @@ float GetPlayerColorForce(float i)
 
 float GetPlayerColor(float i)
 {
-       if not(playerslots[i].gotscores) // unconnected
+       if(!playerslots[i].gotscores) // unconnected
                return NUM_SPECTATOR;
        else if(stof(getplayerkeyvalue(i, "frags")) == FRAGS_SPECTATOR)
                return NUM_SPECTATOR;
@@ -264,7 +264,7 @@ HUD panels
 
 // draw the background/borders
 #define HUD_Panel_DrawBg(theAlpha)\
-if(panel.current_panel_bg != "0")\
+if(panel.current_panel_bg != "0" && panel.current_panel_bg != "")\
        draw_BorderPicture(panel_pos - '1 1 0' * panel_bg_border, panel.current_panel_bg, panel_size + '1 1 0' * 2 * panel_bg_border, panel_bg_color, panel_bg_alpha * theAlpha, '1 1 0' * (panel_bg_border/BORDER_MULTIPLIER))
 
 //basically the same code of draw_ButtonPicture and draw_VertButtonPicture for the menu
@@ -447,8 +447,7 @@ float GetAmmoTypeForWep(float i)
 void HUD_Weapons(void)
 {
        // declarations
-       WEPSET_DECLARE_A(weapons_stat);
-       WEPSET_COPY_AS(weapons_stat);
+       WepSet weapons_stat = WepSet_GetFromStat();
        float i, f, a;
        float screen_ar, center_x = 0, center_y;
        float weapon_count, weapon_id;
@@ -531,9 +530,9 @@ void HUD_Weapons(void)
        {
                if(autocvar__hud_configure)
                {
-                       if (WEPSET_EMPTY_A(weapons_stat))
+                       if (!weapons_stat)
                                for(i = WEP_FIRST; i <= WEP_LAST; i += floor((WEP_LAST-WEP_FIRST)/5))
-                                       WEPSET_OR_AW(weapons_stat, i);
+                                       weapons_stat |= WepSet_FromWeapon(i);
 
                        if(menu_enabled != 2)
                                HUD_Panel_DrawBg(1); // also draw the bg of the entire panel
@@ -542,7 +541,7 @@ void HUD_Weapons(void)
                // do we own this weapon?
                weapon_count = 0;
                for(i = 0; i <= WEP_LAST-WEP_FIRST; ++i)
-                       if(WEPSET_CONTAINS_AW(weapons_stat, weaponorder[i].weapon))
+                       if(weapons_stat & WepSet_FromWeapon(weaponorder[i].weapon))
                                ++weapon_count;
 
                // add it anyway if weaponcomplain is shown
@@ -568,7 +567,7 @@ void HUD_Weapons(void)
                weapon_size_y = old_panel_size_y / rows;
 
                // change table values to include only the owned weapons
-               // weapon_size won't be changed
+               float columns_save = columns;
                if(weapon_count <= rows)
                {
                        rows = weapon_count;
@@ -577,6 +576,10 @@ void HUD_Weapons(void)
                else
                        columns = ceil(weapon_count / rows);
 
+               // enlarge weapon_size to match desired aspect ratio in order to capitalize on panel space
+               if(columns < columns_save)
+                       weapon_size_x = min(old_panel_size_x / columns, aspect * weapon_size_y);
+
                // reduce size of the panel
                panel_size_x = columns * weapon_size_x;
                panel_size_y = rows * weapon_size_y;
@@ -734,12 +737,12 @@ void HUD_Weapons(void)
 
                // skip this weapon if we don't own it (and onlyowned is enabled)-- or if weapons_complainbubble is showing for this weapon
                if(autocvar_hud_panel_weapons_onlyowned)
-               if not(WEPSET_CONTAINS_AW(weapons_stat, self.weapon) || (self.weapon == complain_weapon))
+               if (!((weapons_stat & WepSet_FromWeapon(self.weapon)) || (self.weapon == complain_weapon)))
                        continue;
 
                // figure out the drawing position of weapon
-               weapon_pos = (panel_pos 
-                       + eX * column * weapon_size_x 
+               weapon_pos = (panel_pos
+                       + eX * column * weapon_size_x
                        + eY * row * weapon_size_y);
 
                // draw background behind currently selected weapon
@@ -758,7 +761,7 @@ void HUD_Weapons(void)
                }
 
                // drawing all the weapon items
-               if(WEPSET_CONTAINS_AW(weapons_stat, self.weapon))
+               if(weapons_stat & WepSet_FromWeapon(self.weapon))
                {
                        // draw the weapon image
                        drawpic_aspect_skin(weapon_pos, strcat("weapon", self.netname), weapon_size, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
@@ -1133,7 +1136,7 @@ void HUD_Powerups(void)
        {
                if(!autocvar_hud_panel_powerups) return;
                if(spectatee_status == -1) return;
-               if not(getstati(STAT_ITEMS, 0, 24) & (IT_STRENGTH | IT_INVINCIBLE | IT_SUPERWEAPON)) return;
+               if(!(getstati(STAT_ITEMS, 0, 24) & (IT_STRENGTH | IT_INVINCIBLE | IT_SUPERWEAPON))) return;
                if (getstati(STAT_HEALTH) <= 0) return;
 
                strength_time = bound(0, getstatf(STAT_STRENGTH_FINISHED) - time, 99);
@@ -1405,7 +1408,7 @@ void HUD_HealthArmor(void)
        if(autocvar_hud_panel_healtharmor == 2) // combined health and armor display
        {
                vector v;
-               v = healtharmor_maxdamage(health, armor, armorblockpercent);
+               v = healtharmor_maxdamage(health, armor, armorblockpercent, DEATH_WEAPON);
 
                float x;
                x = floor(v_x + 1);
@@ -1522,7 +1525,7 @@ void HUD_HealthArmor(void)
                                        {
                                                float BLINK_FACTOR = 0.15;
                                                float BLINK_BASE = 0.85;
-                                               float BLINK_FREQ = 9; 
+                                               float BLINK_FREQ = 9;
                                                pain_health_alpha = BLINK_BASE + BLINK_FACTOR * cos(time * BLINK_FREQ);
                                        }
                                }
@@ -1598,148 +1601,154 @@ void HUD_HealthArmor(void)
 
 void HUD_Notify_Push(string icon, string attacker, string victim)
 {
-       if(icon != "")
-       {
-               --kn_index;
-               if (kn_index == -1) { kn_index = KN_MAX_ENTRIES-1; }
-               notify_times[kn_index] = time;
+       if (icon == "")
+               return;
+
+       ++notify_count;
+       --notify_index;
+
+       if (notify_index == -1)
+               notify_index = NOTIFY_MAX_ENTRIES-1;
 
-               // icon
-               if(notify_icon[kn_index]) { strunzone(notify_icon[kn_index]); }
-               notify_icon[kn_index] = strzone(icon);
+       // Free old strings
+       if (notify_attackers[notify_index])
+               strunzone(notify_attackers[notify_index]);
 
-               // attacker
-               if(notify_attackers[kn_index]) { strunzone(notify_attackers[kn_index]); }
-               notify_attackers[kn_index] = strzone(attacker);
+       if (notify_victims[notify_index])
+               strunzone(notify_victims[notify_index]);
 
-               // victim
-               if(notify_victims[kn_index]) { strunzone(notify_victims[kn_index]); }
-               notify_victims[kn_index] = strzone(victim);
+       if (notify_icons[notify_index])
+               strunzone(notify_icons[notify_index]);
+
+       // Allocate new strings
+       if (victim != "")
+       {
+               notify_attackers[notify_index] = strzone(attacker);
+               notify_victims[notify_index] = strzone(victim);
        }
+       else
+       {
+               // In case of a notification without a victim, the attacker
+               // is displayed on the victim's side. Instead of special
+               // treatment later on, we can simply switch them here.
+               notify_attackers[notify_index] = string_null;
+               notify_victims[notify_index] = strzone(attacker);
+       }
+
+       notify_icons[notify_index] = strzone(icon);
+       notify_times[notify_index] = time;
 }
 
 void HUD_Notify(void)
 {
-       if(!autocvar__hud_configure)
-       {
-               if(!autocvar_hud_panel_notify) return;
-       }
+       if (!autocvar__hud_configure)
+               if (!autocvar_hud_panel_notify)
+                       return;
 
        HUD_Panel_UpdateCvars();
-       vector pos, mySize;
-       pos = panel_pos;
-       mySize = panel_size;
-
        HUD_Panel_DrawBg(1);
-       if(panel_bg_padding)
+
+       if (!autocvar__hud_configure)
+               if (notify_count == 0)
+                       return;
+
+       vector pos, size;
+       pos  = panel_pos;
+       size = panel_size;
+
+       if (panel_bg_padding)
        {
-               pos += '1 1 0' * panel_bg_padding;
-               mySize -= '2 2 0' * panel_bg_padding;
+               pos  += '1 1 0' * panel_bg_padding;
+               size -= '2 2 0' * panel_bg_padding;
        }
 
-       float entries, height;
-       entries = bound(1, floor(KN_MAX_ENTRIES * mySize_y/mySize_x), KN_MAX_ENTRIES);
-       height = mySize_y/entries;
-       
-       vector fontsize;
-       float fontheight = height * autocvar_hud_panel_notify_fontsize;
-       fontsize = '0.5 0.5 0' * fontheight;
+       float fade_start = max(0, autocvar_hud_panel_notify_time);
+       float fade_time = max(0, autocvar_hud_panel_notify_fadetime);
+       float icon_aspect = max(1, autocvar_hud_panel_notify_icon_aspect);
 
-       float a;
-       float when;
-       when = autocvar_hud_panel_notify_time;
-       float fadetime;
-       fadetime = autocvar_hud_panel_notify_fadetime;
+       float entry_count = bound(1, floor(NOTIFY_MAX_ENTRIES * size_y / size_x), NOTIFY_MAX_ENTRIES);
+       float entry_height = size_y / entry_count;
+
+       float panel_width_half = size_x * 0.5;
+       float icon_width_half = entry_height * icon_aspect / 2;
+       float name_maxwidth = panel_width_half - icon_width_half - size_x * NOTIFY_ICON_MARGIN;
 
-       vector pos_attacker, pos_victim, pos_icon;
-       float width_attacker;
+       vector font_size = '0.5 0.5 0' * entry_height * autocvar_hud_panel_notify_fontsize;
+       vector icon_size = (eX * icon_aspect + eY) * entry_height;
+       vector icon_left = eX * (panel_width_half - icon_width_half);
+       vector attacker_right = eX * name_maxwidth;
+       vector victim_left = eX * (size_x - name_maxwidth);
+
+       vector attacker_pos, victim_pos, icon_pos;
        string attacker, victim, icon;
+       float i, j, count, step, limit, alpha;
 
-       float i, j, step, limit;
-       if(autocvar_hud_panel_notify_flip) //order items from the top down
+       if (autocvar_hud_panel_notify_flip)
        {
+               // Order items from the top down
                i = 0;
                step = +1;
-               limit = entries;
+               limit = entry_count;
        }
-       else //order items from the bottom up
+       else
        {
-               i = entries - 1;
+               // Order items from the bottom up
+               i = entry_count - 1;
                step = -1;
                limit = -1;
        }
 
-       for(j = kn_index;  i != limit;  i += step, ++j)
+       for (j = notify_index, count = 0; i != limit; i += step, ++j, ++count)
        {
                if(autocvar__hud_configure)
                {
-                       if (step == +1)
-                               a = i;
-                       else // inverse order
-                               a = entries - 1 - i;
-                       attacker = textShortenToWidth(sprintf(_("Player %d"), a+1), 0.48 * mySize_x - height, fontsize, stringwidth_colors);
-                       victim = textShortenToWidth(sprintf(_("Player %d"), a+2), 0.48 * mySize_x - height, fontsize, stringwidth_colors);
-                       icon = strcat("weapon", get_weaponinfo(WEP_FIRST + mod(floor(a*2.4), WEP_LAST)).netname);
-                       a = bound(0, (when - a) / 4, 1);
-                       goto hud_config_notifyprint;
+                       attacker = sprintf(_("Player %d"), count + 1);
+                       victim = sprintf(_("Player %d"), count + 2);
+                       icon = strcat("weapon", get_weaponinfo(min(WEP_FIRST + count * 2, WEP_LAST)).netname);
+                       alpha = bound(0, 1.2 - count / entry_count, 1);
                }
                else
                {
-                       if (j == KN_MAX_ENTRIES)
+                       if (j == NOTIFY_MAX_ENTRIES)
                                j = 0;
 
-                       if(notify_times[j] + when > time)
-                               a = 1;
-                       else if(fadetime)
+                       if (notify_times[j] + fade_start > time)
+                               alpha = 1;
+                       else if (fade_time != 0)
                        {
-                               a = bound(0, (notify_times[j] + when + fadetime - time) / fadetime, 1);
-                               if(!a)
-                               {
+                               alpha = bound(0, (notify_times[j] + fade_start + fade_time - time) / fade_time, 1);
+                               if (alpha == 0)
                                        break;
-                               }
                        }
                        else
-                       {
                                break;
-                       }
-                       
+
                        attacker = notify_attackers[j];
                        victim = notify_victims[j];
-                       icon = notify_icon[j];
+                       icon = notify_icons[j];
                }
 
-               //type = notify_deathtype[j];
-               //w = DEATH_WEAPONOF(type);
-
-               if(icon != "")
+               if (icon != "" && victim != "")
                {
-                       if((attacker != "") && (victim == ""))
-                       {
-                               // Y [used by] X
-                               attacker = textShortenToWidth(attacker, 0.73 * mySize_x - height, fontsize, stringwidth_colors);
-                               pos_attacker = pos + eX * (0.27 * mySize_x + height) + eY * ((0.5 * fontsize_y + i * height) + (0.5 * (height - fontheight)));
-                               pos_icon = pos + eX * 0.25 * mySize_x - eX * height + eY * i * height;
+                       vector name_top = eY * (i * entry_height + 0.5 * (entry_height - font_size_y));
 
-                               drawpic_aspect_skin(pos_icon, icon, '2 1 0' * height, '1 1 1', panel_fg_alpha * a, DRAWFLAG_NORMAL);
-                               drawcolorcodedstring(pos_attacker, attacker, fontsize, panel_fg_alpha * a, DRAWFLAG_NORMAL);
-                       }
-                       else if((attacker != "") && (victim != ""))
+                       icon_pos = pos + icon_left + eY * i * entry_height;
+                       drawpic_aspect_skin(icon_pos, icon, icon_size, '1 1 1', panel_fg_alpha * alpha, DRAWFLAG_NORMAL);
+
+                       victim = textShortenToWidth(victim, name_maxwidth, font_size, stringwidth_colors);
+                       victim_pos = pos + victim_left + name_top;
+                       drawcolorcodedstring(victim_pos, victim, font_size, panel_fg_alpha * alpha, DRAWFLAG_NORMAL);
+
+                       if (attacker != "")
                        {
-                               // X [did action to] Y
-                               attacker = textShortenToWidth(attacker, 0.48 * mySize_x - height, fontsize, stringwidth_colors);
-                               victim = textShortenToWidth(victim, 0.48 * mySize_x - height, fontsize, stringwidth_colors);
-:hud_config_notifyprint
-                               width_attacker = stringwidth(attacker, TRUE, fontsize);
-                               pos_attacker = pos + eX * (0.48 * mySize_x - height - width_attacker) + eY * ((0.5 * fontsize_y + i * height) + (0.5 * (height - fontheight)));
-                               pos_victim = pos + eX * (0.52 * mySize_x + height) + eY * ((0.5 * fontsize_y + i * height) + (0.5 * (height - fontheight)));
-                               pos_icon = pos + eX * 0.5 * mySize_x - eX * height + eY * i * height;
-
-                               drawpic_aspect_skin(pos_icon, icon, '2 1 0' * height, '1 1 1', panel_fg_alpha * a, DRAWFLAG_NORMAL);
-                               drawcolorcodedstring(pos_attacker, attacker, fontsize, panel_fg_alpha * a, DRAWFLAG_NORMAL);
-                               drawcolorcodedstring(pos_victim, victim, fontsize, panel_fg_alpha * a, DRAWFLAG_NORMAL);
+                               attacker = textShortenToWidth(attacker, name_maxwidth, font_size, stringwidth_colors);
+                               attacker_pos = pos + attacker_right - eX * stringwidth(attacker, TRUE, font_size) + name_top;
+                               drawcolorcodedstring(attacker_pos, attacker, font_size, panel_fg_alpha * alpha, DRAWFLAG_NORMAL);
                        }
                }
        }
+
+       notify_count = count;
 }
 
 // Timer (#5)
@@ -1825,6 +1834,11 @@ void HUD_Radar(void)
                {
                        if (autocvar_hud_panel_radar == 0) return;
                        if (autocvar_hud_panel_radar != 2 && !teamplay) return;
+                       if(radar_panel_modified)
+                       {
+                               panel.update_time = time; // forces reload of panel attributes
+                               radar_panel_modified = false;
+                       }
                }
        }
 
@@ -1844,6 +1858,8 @@ void HUD_Radar(void)
                panel_bg = strcat(hud_skin_path, "/border_default"); // always use the default border when maximized
                if(precache_pic(panel_bg) == "")
                        panel_bg = "gfx/hud/default/border_default"; // fallback
+               if(!radar_panel_modified && panel_bg != panel.current_panel_bg)
+                       radar_panel_modified = true;
                if(panel.current_panel_bg)
                        strunzone(panel.current_panel_bg);
                panel.current_panel_bg = strzone(panel_bg);
@@ -1864,7 +1880,7 @@ void HUD_Radar(void)
                                f = 1;
                                break;
                }
-               
+
                switch(hud_panel_radar_maximized_rotation)
                {
                        case 0:
@@ -1893,7 +1909,7 @@ void HUD_Radar(void)
                                f = 1;
                                break;
                }
-               
+
                switch(hud_panel_radar_rotation)
                {
                        case 0:
@@ -2273,7 +2289,7 @@ void HUD_Score(void)
                        score = tm.(teamscores[ts_primary]);
                        if(autocvar__hud_configure)
                                score = 123;
-                       
+
                        if (score > max_fragcount)
                                max_fragcount = score;
 
@@ -2721,7 +2737,7 @@ void HUD_Mod_CTF(vector pos, vector mySize)
        stat_items = getstati(STAT_ITEMS, 0, 24);
        redflag = (stat_items/IT_RED_FLAG_TAKEN) & 3;
        blueflag = (stat_items/IT_BLUE_FLAG_TAKEN) & 3;
-       
+
        if(redflag || blueflag)
                mod_active = 1;
        else
@@ -2998,29 +3014,29 @@ void HUD_Mod_KH(vector pos, vector mySize)
 float kaball_prevstatus; // last remembered status
 float kaball_statuschange_time; // time when the status changed
 
-// we don't need to reset for keepaway since it immediately 
+// we don't need to reset for keepaway since it immediately
 // autocorrects prevstatus as to if the player has the ball or not
 
 void HUD_Mod_Keepaway(vector pos, vector mySize)
 {
        mod_active = 1; // keepaway should always show the mod HUD
-       
+
        float BLINK_FACTOR = 0.15;
        float BLINK_BASE = 0.85;
-       float BLINK_FREQ = 5; 
+       float BLINK_FREQ = 5;
        float kaball_alpha = BLINK_BASE + BLINK_FACTOR * cos(time * BLINK_FREQ);
-       
+
        float stat_items = getstati(STAT_ITEMS, 0, 24);
        float kaball = (stat_items/IT_KEY1) & 1;
-       
+
        if(kaball != kaball_prevstatus)
        {
                kaball_statuschange_time = time;
                kaball_prevstatus = kaball;
        }
-       
+
        vector kaball_pos, kaball_size;
-       
+
        if(mySize_x > mySize_y) {
                kaball_pos = pos + eX * 0.25 * mySize_x;
                kaball_size = eX * 0.5 * mySize_x + eY * mySize_y;
@@ -3028,13 +3044,13 @@ void HUD_Mod_Keepaway(vector pos, vector mySize)
                kaball_pos = pos + eY * 0.25 * mySize_y;
                kaball_size = eY * 0.5 * mySize_y + eX * mySize_x;
        }
-       
+
        float kaball_statuschange_elapsedtime = time - kaball_statuschange_time;
        float f = bound(0, kaball_statuschange_elapsedtime*2, 1);
-       
+
        if(kaball_prevstatus && f < 1)
                drawpic_aspect_skin_expanding(kaball_pos, "keepawayball_carrying", kaball_size, '1 1 1', panel_fg_alpha * kaball_alpha, DRAWFLAG_NORMAL, f);
-       
+
        if(kaball)
                drawpic_aspect_skin(pos, "keepawayball_carrying", eX * mySize_x + eY * mySize_y, '1 1 1', panel_fg_alpha * kaball_alpha * f, DRAWFLAG_NORMAL);
 }
@@ -3087,7 +3103,7 @@ void HUD_Mod_Race(vector pos, vector mySize)
        float f; // yet another function has this
        score = me.(scores[ps_primary]);
 
-       if not((scores_flags[ps_primary] & SFL_TIME) && !teamplay) // race/cts record display on HUD
+       if(!(scores_flags[ps_primary] & SFL_TIME) || teamplay) // race/cts record display on HUD
                return; // no records in the actual race
 
        // clientside personal record
@@ -3103,7 +3119,7 @@ void HUD_Mod_Race(vector pos, vector mySize)
                if(autocvar_cl_autodemo_delete_keeprecords)
                {
                        f = autocvar_cl_autodemo_delete;
-                       f &~= 1;
+                       f &= ~1;
                        cvar_set("cl_autodemo_delete", ftos(f)); // don't delete demo with new record!
                }
        }
@@ -3452,7 +3468,14 @@ void HUD_Chat(void)
                        return;
                }
                if(autocvar__con_chat_maximized)
+               {
                        if(!hud_draw_maximized) return;
+               }
+               else if(chat_panel_modified)
+               {
+                       panel.update_time = time; // forces reload of panel attributes
+                       chat_panel_modified = false;
+               }
        }
 
        HUD_Panel_UpdateCvars();
@@ -3470,6 +3493,7 @@ void HUD_Chat(void)
                        if(panel.current_panel_bg)
                                strunzone(panel.current_panel_bg);
                        panel.current_panel_bg = strzone(panel_bg);
+                       chat_panel_modified = true;
                }
                panel_bg_alpha = max(0.75, panel_bg_alpha); // force an theAlpha of at least 0.75
        }
@@ -3548,7 +3572,7 @@ void HUD_EngineInfo(void)
                frametimeavg = (frametimeavg + frametimeavg1 + frametimeavg2 + currentframetime)/4; // average three frametimes into framecounter for slightly more stable fps readings :P
                frametimeavg2 = frametimeavg1;
                frametimeavg1 = frametimeavg;
-               
+
                float weight;
                weight = cvar("hud_panel_engineinfo_framecounter_exponentialmovingaverage_new_weight");
                if(currentframetime > 0.0001) // filter out insane values which sometimes seem to occur and throw off the average? If you are getting 10,000 fps or more, then you don't need a framerate counter.
@@ -3625,7 +3649,7 @@ void HUD_InfoMessages(void)
 
        vector fontsize;
        fontsize = '0.20 0.20 0' * mySize_y;
-       
+
        float a;
        a = panel_fg_alpha;
 
@@ -3660,9 +3684,7 @@ void HUD_InfoMessages(void)
                        s = sprintf(_("^1Press ^3%s^1 for gamemode info"), getcommandkey("server info", "+show_info"));
                        drawInfoMessage(s)
 
-                       if(gametype == MAPINFO_TYPE_ARENA)
-                               s = _("^1Wait for your turn to join");
-                       else if(gametype == MAPINFO_TYPE_LMS)
+                       if(gametype == MAPINFO_TYPE_LMS)
                        {
                                entity sk;
                                sk = playerslots[player_localnum];
@@ -3751,7 +3773,7 @@ void HUD_InfoMessages(void)
                        }
                }
        }
-       else 
+       else
        {
                s = _("^7Press ^3ESC ^7to show HUD options.");
                drawInfoMessage(s)
@@ -3826,7 +3848,7 @@ void HUD_Physics(void)
                        conversion_factor = 0.0254 * 1.943844492; // 1 m/s = 1.943844492 knots, because 1 knot = 1.852 km/h
                        break;
        }
-       
+
        vector vel = (csqcplayer ? csqcplayer.velocity : pmove_vel);
 
        float max_speed = floor( autocvar_hud_panel_physics_speed_max * conversion_factor + 0.5 );
@@ -3849,9 +3871,9 @@ void HUD_Physics(void)
                        acceleration = (vlen(vel) - vlen(acc_prevspeed));
                else
                        acceleration = (vlen(vel - '0 0 1' * vel_z) - vlen(acc_prevspeed - '0 0 1' * acc_prevspeed_z));
-               
+
                acceleration = acceleration * (1 / max(0.0001, f)) * (0.0254 / 9.80665);
-               
+
                acc_prevspeed = vel;
                acc_prevtime = time;
 
@@ -4047,7 +4069,7 @@ float centerprint_showing;
 
 void centerprint_generic(float new_id, string strMessage, float duration, float countdown_num)
 {
-       //print(sprintf("centerprint_generic(%d, '%s^7', %d, %d);\n", new_id, strMessage, duration, countdown_num));
+       //printf("centerprint_generic(%d, '%s^7', %d, %d);\n", new_id, strMessage, duration, countdown_num);
        float i, j;
 
        if(strMessage == "" && new_id == 0)
@@ -4158,7 +4180,7 @@ void HUD_CenterPrint (void)
                        float r;
                        r = random();
                        if (r > 0.9)
-                               centerprint_generic(floor(r*1000), strcat(sprintf("^3Countdown message at time %s", seconds_tostring(time)), ", seconds left: %d"), 1, 10);
+                               centerprint_generic(floor(r*1000), strcat(sprintf("^3Countdown message at time %s", seconds_tostring(time)), ", seconds left: ^COUNT"), 1, 10);
                        else if (r > 0.8)
                                centerprint_generic(0, sprintf("^1Multiline message at time %s that\n^1lasts longer than normal", seconds_tostring(time)), 20, 0);
                        else
@@ -4184,9 +4206,9 @@ void HUD_CenterPrint (void)
                if (scoreboard_bottom >= 0.96 * vid_conheight)
                        return;
                vector target_pos;
-               
+
                target_pos = eY * scoreboard_bottom + eX * 0.5 * (vid_conwidth - panel_size_x);
-               
+
                if(target_pos_y > panel_pos_y)
                {
                        panel_pos = panel_pos + (target_pos - panel_pos) * sqrt(scoreboard_fade_alpha);
@@ -4218,8 +4240,7 @@ void HUD_CenterPrint (void)
        float a, sz, align, current_msg_pos_y = 0, msg_size;
        vector pos;
        string ts;
-
-       n = -1; // if no msg will be displayed, n stays -1
+       float all_messages_expired = TRUE;
 
        pos = panel_pos;
        if (autocvar_hud_panel_centerprint_flip)
@@ -4242,31 +4263,36 @@ void HUD_CenterPrint (void)
                                continue;
                }
 
+               all_messages_expired = FALSE;
 
-               // fade the centerprint_hud in/out 
-               if(centerprint_time[j] < 0)
-                       a = bound(0, (time - centerprint_expire_time[j]) / max(0.0001, autocvar_hud_panel_centerprint_fade_in), 1);
-               else if(centerprint_expire_time[j] - autocvar_hud_panel_centerprint_fade_out > time)
-                       a = bound(0, (time - (centerprint_expire_time[j] - centerprint_time[j])) / max(0.0001, autocvar_hud_panel_centerprint_fade_in), 1);
-               else if(centerprint_expire_time[j] > time)
+               // fade the centerprint_hud in/out
+               if(centerprint_time[j] < 0)  // Expired but forced. Expire time is the fade-in time.
+                       a = (time - centerprint_expire_time[j]) / max(0.0001, autocvar_hud_panel_centerprint_fade_in);
+               else if(centerprint_expire_time[j] - autocvar_hud_panel_centerprint_fade_out > time)  // Regularily printed. Not fading out yet.
+                       a = (time - (centerprint_expire_time[j] - centerprint_time[j])) / max(0.0001, autocvar_hud_panel_centerprint_fade_in);
+               else // Expiring soon, so fade it out.
                        a = (centerprint_expire_time[j] - time) / max(0.0001, autocvar_hud_panel_centerprint_fade_out);
-               else
-                       a = 0;
-               
+
+               if (a <= 0.5/255.0)  // Guaranteed invisible - don't show.
+                       continue;
+               if (a > 1)
+                       a = 1;
+
                // set the size from fading in/out before subsequent fading
-               sz = autocvar_hud_panel_centerprint_fade_minfontsize + a * (1 - autocvar_hud_panel_centerprint_fade_minfontsize); 
-               
+               sz = autocvar_hud_panel_centerprint_fade_minfontsize + a * (1 - autocvar_hud_panel_centerprint_fade_minfontsize);
+
                // also fade it based on positioning
                if(autocvar_hud_panel_centerprint_fade_subsequent)
                {
                        a = a * bound(autocvar_hud_panel_centerprint_fade_subsequent_passone_minalpha, (1 - (g / max(1, autocvar_hud_panel_centerprint_fade_subsequent_passone))), 1); // pass one: all messages after the first have half theAlpha
                        a = a * bound(autocvar_hud_panel_centerprint_fade_subsequent_passtwo_minalpha, (1 - (g / max(1, autocvar_hud_panel_centerprint_fade_subsequent_passtwo))), 1); // pass two: after that, gradually lower theAlpha even more for each message
                }
-               
+               a *= panel_fg_alpha;
+
                // finally set the size based on the new theAlpha from subsequent fading
-               sz = sz * (autocvar_hud_panel_centerprint_fade_subsequent_minfontsize + a * (1 - autocvar_hud_panel_centerprint_fade_subsequent_minfontsize)); 
+               sz = sz * (autocvar_hud_panel_centerprint_fade_subsequent_minfontsize + a * (1 - autocvar_hud_panel_centerprint_fade_subsequent_minfontsize));
                drawfontscale = sz * '1 1 0';
-               
+
                if (centerprint_countdown_num[j])
                        n = tokenizebyseparator(strreplace("^COUNT", count_seconds(centerprint_countdown_num[j]), centerprint_messages[j]), "\n");
                else
@@ -4301,7 +4327,8 @@ void HUD_CenterPrint (void)
                                {
                                        if (align)
                                                pos_x = panel_pos_x + (panel_size_x - stringwidth(ts, TRUE, fontsize)) * align;
-                                       drawcolorcodedstring(pos + eY * 0.5 * (1 - sz) * fontsize_y, ts, fontsize, a * panel_fg_alpha, DRAWFLAG_NORMAL);
+                                       if (a > 0.5/255.0)  // Otherwise guaranteed invisible - don't show. This is checked a second time after some multiplications with other factors were done so temporary changes of these cannot cause flicker.
+                                               drawcolorcodedstring(pos + eY * 0.5 * (1 - sz) * fontsize_y, ts, fontsize, a, DRAWFLAG_NORMAL);
                                        pos_y += fontsize_y;
                                }
                                else
@@ -4309,15 +4336,15 @@ void HUD_CenterPrint (void)
                        }
                }
 
-               ++g; // move next position number up 
-               
+               ++g; // move next position number up
+
                msg_size = pos_y - msg_size;
                if (autocvar_hud_panel_centerprint_flip)
                {
                        pos_y = current_msg_pos_y - CENTERPRINT_SPACING * fontsize_y;
                        if (a < 1 && centerprint_msgID[j] == 0) // messages with id can be replaced just after they are faded out, so never move over them the next messages
                                pos_y += (msg_size + CENTERPRINT_SPACING * fontsize_y) * (1 - sqrt(sz));
-                               
+
                        if (pos_y < panel_pos_y) // check if the next message can be shown
                        {
                                drawfontscale = '1 1 0';
@@ -4329,7 +4356,7 @@ void HUD_CenterPrint (void)
                        pos_y += CENTERPRINT_SPACING * fontsize_y;
                        if (a < 1 && centerprint_msgID[j] == 0) // messages with id can be replaced just after they are faded out, so never move over them the next messages
                                pos_y -= (msg_size + CENTERPRINT_SPACING * fontsize_y) * (1 - sqrt(sz));
-                               
+
                        if(pos_y > panel_pos_y + panel_size_y - fontsize_y) // check if the next message can be shown
                        {
                                drawfontscale = '1 1 0';
@@ -4338,13 +4365,73 @@ void HUD_CenterPrint (void)
                }
        }
        drawfontscale = '1 1 0';
-       if (n == -1)
+       if (all_messages_expired)
        {
                centerprint_showing = FALSE;
                reset_centerprint_messages();
        }
 }
 
+// Buffs (#18)
+//
+void HUD_Buffs(void)
+{
+       float buffs = getstati(STAT_BUFFS, 0, 24);
+       if(!autocvar__hud_configure)
+       {
+               if(!autocvar_hud_panel_buffs) return;
+               if(spectatee_status == -1) return;
+               if(getstati(STAT_HEALTH) <= 0) return;
+               if(!buffs) return;
+       }
+       else
+       {
+               buffs = Buff_Type_first.items; // force first buff
+       }
+       
+       float b = 0; // counter to tell other functions that we have buffs
+       entity e;
+       string s = "";
+       for(e = Buff_Type_first; e; e = e.enemy) if(buffs & e.items)
+       {
+               ++b;
+               string o = strcat(rgb_to_hexcolor(Buff_Color(e.items)), Buff_PrettyName(e.items));
+               if(s == "")
+                       s = o;
+               else
+                       s = strcat(s, " ", o);
+       }
+
+       HUD_Panel_UpdateCvars();
+
+       draw_beginBoldFont();
+
+       vector pos, mySize;
+       pos = panel_pos;
+       mySize = panel_size;
+
+       HUD_Panel_DrawBg(bound(0, b, 1));
+       if(panel_bg_padding)
+       {
+               pos += '1 1 0' * panel_bg_padding;
+               mySize -= '2 2 0' * panel_bg_padding;
+       }
+
+       //float panel_ar = mySize_x/mySize_y;
+       //float is_vertical = (panel_ar < 1);
+       //float buff_iconalign = autocvar_hud_panel_buffs_iconalign;
+       vector buff_offset = '0 0 0';
+       
+       for(e = Buff_Type_first; e; e = e.enemy) if(buffs & e.items)
+       {
+               //DrawNumIcon(pos + buff_offset, mySize, shield, "shield", is_vertical, buff_iconalign, '1 1 1', 1);
+               drawcolorcodedstring_aspect(pos + buff_offset, s, mySize, panel_fg_alpha * 0.5, DRAWFLAG_NORMAL);
+       }
+
+       draw_endBoldFont();
+}
+
+
 /*
 ==================
 Main HUD system