]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into terencehill/scoreboard_item_stats
authorMario <mario.mario@y7mail.com>
Sun, 18 Oct 2020 23:55:38 +0000 (09:55 +1000)
committerMario <mario.mario@y7mail.com>
Sun, 18 Oct 2020 23:55:38 +0000 (09:55 +1000)
1  2 
qcsrc/client/hud/panel/scoreboard.qc
qcsrc/common/items/inventory.qh

index 51c7a14c350c86f118d78cd3dd14438d61f40a9d,7f3c8e5c28a3596d424a39d074d93eff89a96333..c474ad33b8c0b2cb4f8dc574e479bab745bac3e1
@@@ -1,22 -1,39 +1,40 @@@
  #include "scoreboard.qh"
  
  #include <client/autocvars.qh>
- #include <client/defs.qh>
- #include <client/main.qh>
- #include <client/miscfunctions.qh>
- #include "quickmenu.qh"
- #include <common/ent_cs.qh>
+ #include <client/draw.qh>
+ #include <client/hud/panel/quickmenu.qh>
+ #include <client/hud/panel/racetimer.qh>
+ #include <client/hud/panel/weapons.qh>
  #include <common/constants.qh>
- #include <common/net_linked.qh>
+ #include <common/ent_cs.qh>
  #include <common/mapinfo.qh>
  #include <common/minigames/cl_minigames.qh>
+ #include <common/net_linked.qh>
  #include <common/scores.qh>
  #include <common/stats.qh>
  #include <common/teams.qh>
 +#include <common/items/inventory.qh>
  
  // Scoreboard (#24)
  
+ void Scoreboard_Draw_Export(int fh)
+ {
+       // allow saving cvars that aesthetically change the panel into hud skin files
+       HUD_Write_Cvar("hud_panel_scoreboard_fadeinspeed");
+       HUD_Write_Cvar("hud_panel_scoreboard_fadeoutspeed");
+       HUD_Write_Cvar("hud_panel_scoreboard_respawntime_decimals");
+       HUD_Write_Cvar("hud_panel_scoreboard_table_bg_alpha");
+       HUD_Write_Cvar("hud_panel_scoreboard_table_bg_scale");
+       HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha");
+       HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha_self");
+       HUD_Write_Cvar("hud_panel_scoreboard_table_highlight");
+       HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha");
+       HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha_self");
+       HUD_Write_Cvar("hud_panel_scoreboard_bg_teams_color_team");
+       HUD_Write_Cvar("hud_panel_scoreboard_accuracy_doublerows");
+       HUD_Write_Cvar("hud_panel_scoreboard_accuracy_nocolors");
+ }
  const int MAX_SBT_FIELDS = MAX_SCORE;
  
  PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
@@@ -66,8 -83,6 +84,6 @@@ bool autocvar_hud_panel_scoreboard_accu
  float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
  float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
  
- bool autocvar_hud_panel_scoreboard_ctf_leaderboard = true;
  bool autocvar_hud_panel_scoreboard_dynamichud = false;
  
  float autocvar_hud_panel_scoreboard_maxheight = 0.6;
@@@ -85,48 -100,48 +101,48 @@@ string Label_getInfo(string label, int 
  
        switch(label)
        {
-               case "bckills":      if (!mode) return CTX(_("SCO^bckills"));      else LOG_INFO(strcat("^3", "bckills", "            ^7", _("Number of ball carrier kills")));
-               case "bctime":       if (!mode) return CTX(_("SCO^bctime"));       else LOG_INFO(strcat("^3", "bctime", "             ^7", _("Total amount of time holding the ball in Keepaway")));
-               case "caps":         if (!mode) return CTX(_("SCO^caps"));         else LOG_INFO(strcat("^3", "caps", "               ^7", _("How often a flag (CTF) or a key (KeyHunt) was captured")));
-               case "captime":      if (!mode) return CTX(_("SCO^captime"));      else LOG_INFO(strcat("^3", "captime", "            ^7", _("Time of fastest capture (CTF)")));
-               case "deaths":       if (!mode) return CTX(_("SCO^deaths"));       else LOG_INFO(strcat("^3", "deaths", "             ^7", _("Number of deaths")));
-               case "destroyed":    if (!mode) return CTX(_("SCO^destroyed"));    else LOG_INFO(strcat("^3", "destroyed", "          ^7", _("Number of keys destroyed by pushing them into void")));
-               case "dmg":          if (!mode) return CTX(_("SCO^damage"));       else LOG_INFO(strcat("^3", "dmg", "                ^7", _("The total damage done")));
-               case "dmgtaken":     if (!mode) return CTX(_("SCO^dmgtaken"));     else LOG_INFO(strcat("^3", "dmgtaken", "           ^7", _("The total damage taken")));
-               case "drops":        if (!mode) return CTX(_("SCO^drops"));        else LOG_INFO(strcat("^3", "drops", "              ^7", _("Number of flag drops")));
-               case "elo":          if (!mode) return CTX(_("SCO^elo"));          else LOG_INFO(strcat("^3", "elo", "                ^7", _("Player ELO")));
-               case "fastest":      if (!mode) return CTX(_("SCO^fastest"));      else LOG_INFO(strcat("^3", "fastest", "            ^7", _("Time of fastest lap (Race/CTS)")));
-               case "faults":       if (!mode) return CTX(_("SCO^faults"));       else LOG_INFO(strcat("^3", "faults", "             ^7", _("Number of faults committed")));
-               case "fckills":      if (!mode) return CTX(_("SCO^fckills"));      else LOG_INFO(strcat("^3", "fckills", "            ^7", _("Number of flag carrier kills")));
-               case "fps":          if (!mode) return CTX(_("SCO^fps"));          else LOG_INFO(strcat("^3", "fps", "                ^7", _("FPS")));
-               case "frags":        if (!mode) return CTX(_("SCO^frags"));        else LOG_INFO(strcat("^3", "frags", "              ^7", _("Number of kills minus suicides")));
-               case "goals":        if (!mode) return CTX(_("SCO^goals"));        else LOG_INFO(strcat("^3", "goals", "              ^7", _("Number of goals scored")));
-               case "kckills":      if (!mode) return CTX(_("SCO^kckills"));      else LOG_INFO(strcat("^3", "kckills", "            ^7", _("Number of keys carrier kills")));
-               case "kd":           if (!mode) return CTX(_("SCO^k/d"));          else LOG_INFO(strcat("^3", "kd", "                 ^7", _("The kill-death ratio")));
-               case "kdr":          if (!mode) return CTX(_("SCO^kdr"));          else LOG_INFO(strcat("^3", "kdr", "                ^7", _("The kill-death ratio")));
-               case "kdratio":      if (!mode) return CTX(_("SCO^kdratio"));      else LOG_INFO(strcat("^3", "kdratio", "            ^7", _("The kill-death ratio")));
-               case "kills":        if (!mode) return CTX(_("SCO^kills"));        else LOG_INFO(strcat("^3", "kills", "              ^7", _("Number of kills")));
-               case "laps":         if (!mode) return CTX(_("SCO^laps"));         else LOG_INFO(strcat("^3", "laps", "               ^7", _("Number of laps finished (Race/CTS)")));
-               case "lives":        if (!mode) return CTX(_("SCO^lives"));        else LOG_INFO(strcat("^3", "lives", "              ^7", _("Number of lives (LMS)")));
-               case "losses":       if (!mode) return CTX(_("SCO^losses"));       else LOG_INFO(strcat("^3", "losses", "             ^7", _("Number of times a key was lost")));
-               case "name":         if (!mode) return CTX(_("SCO^name"));         else LOG_INFO(strcat("^3", "name", "               ^7", _("Player name")));
-               case "nick":         if (!mode) return CTX(_("SCO^nick"));         else LOG_INFO(strcat("^3", "nick", "               ^7", _("Player name")));
-               case "objectives":   if (!mode) return CTX(_("SCO^objectives"));   else LOG_INFO(strcat("^3", "objectives", "         ^7", _("Number of objectives destroyed")));
-               case "pickups":      if (!mode) return CTX(_("SCO^pickups"));      else LOG_INFO(strcat("^3", "pickups", "            ^7", _("How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up")));
-               case "ping":         if (!mode) return CTX(_("SCO^ping"));         else LOG_INFO(strcat("^3", "ping", "               ^7", _("Ping time")));
-               case "pl":           if (!mode) return CTX(_("SCO^pl"));           else LOG_INFO(strcat("^3", "pl", "                 ^7", _("Packet loss")));
-               case "pushes":       if (!mode) return CTX(_("SCO^pushes"));       else LOG_INFO(strcat("^3", "pushes", "             ^7", _("Number of players pushed into void")));
-               case "rank":         if (!mode) return CTX(_("SCO^rank"));         else LOG_INFO(strcat("^3", "rank", "               ^7", _("Player rank")));
-               case "returns":      if (!mode) return CTX(_("SCO^returns"));      else LOG_INFO(strcat("^3", "returns", "            ^7", _("Number of flag returns")));
-               case "revivals":     if (!mode) return CTX(_("SCO^revivals"));     else LOG_INFO(strcat("^3", "revivals", "           ^7", _("Number of revivals")));
-               case "rounds":       if (!mode) return CTX(_("SCO^rounds won"));   else LOG_INFO(strcat("^3", "rounds", "             ^7", _("Number of rounds won")));
-               case "score":        if (!mode) return CTX(_("SCO^score"));        else LOG_INFO(strcat("^3", "score", "              ^7", _("Total score")));
-               case "suicides":     if (!mode) return CTX(_("SCO^suicides"));     else LOG_INFO(strcat("^3", "suicides", "           ^7", _("Number of suicides")));
-               case "sum":          if (!mode) return CTX(_("SCO^sum"));          else LOG_INFO(strcat("^3", "sum", "                ^7", _("Number of kills minus deaths")));
-               case "takes":        if (!mode) return CTX(_("SCO^takes"));        else LOG_INFO(strcat("^3", "takes", "              ^7", _("Number of domination points taken (Domination)")));
-               case "teamkills":    if (!mode) return CTX(_("SCO^teamkills"));    else LOG_INFO(strcat("^3", "teamkills", "          ^7", _("Number of teamkills")));
-               case "ticks":        if (!mode) return CTX(_("SCO^ticks"));        else LOG_INFO(strcat("^3", "ticks", "              ^7", _("Number of ticks (Domination)")));
-               case "time":         if (!mode) return CTX(_("SCO^time"));         else LOG_INFO(strcat("^3", "time", "               ^7", _("Total time raced (Race/CTS)")));
+               case "bckills":      if (!mode) return CTX(_("SCO^bckills"));      else LOG_HELP(strcat("^3", "bckills", "            ^7", _("Number of ball carrier kills")));
+               case "bctime":       if (!mode) return CTX(_("SCO^bctime"));       else LOG_HELP(strcat("^3", "bctime", "             ^7", _("Total amount of time holding the ball in Keepaway")));
+               case "caps":         if (!mode) return CTX(_("SCO^caps"));         else LOG_HELP(strcat("^3", "caps", "               ^7", _("How often a flag (CTF) or a key (KeyHunt) was captured")));
+               case "captime":      if (!mode) return CTX(_("SCO^captime"));      else LOG_HELP(strcat("^3", "captime", "            ^7", _("Time of fastest capture (CTF)")));
+               case "deaths":       if (!mode) return CTX(_("SCO^deaths"));       else LOG_HELP(strcat("^3", "deaths", "             ^7", _("Number of deaths")));
+               case "destroyed":    if (!mode) return CTX(_("SCO^destroyed"));    else LOG_HELP(strcat("^3", "destroyed", "          ^7", _("Number of keys destroyed by pushing them into void")));
+               case "dmg":          if (!mode) return CTX(_("SCO^damage"));       else LOG_HELP(strcat("^3", "dmg", "                ^7", _("The total damage done")));
+               case "dmgtaken":     if (!mode) return CTX(_("SCO^dmgtaken"));     else LOG_HELP(strcat("^3", "dmgtaken", "           ^7", _("The total damage taken")));
+               case "drops":        if (!mode) return CTX(_("SCO^drops"));        else LOG_HELP(strcat("^3", "drops", "              ^7", _("Number of flag drops")));
+               case "elo":          if (!mode) return CTX(_("SCO^elo"));          else LOG_HELP(strcat("^3", "elo", "                ^7", _("Player ELO")));
+               case "fastest":      if (!mode) return CTX(_("SCO^fastest"));      else LOG_HELP(strcat("^3", "fastest", "            ^7", _("Time of fastest lap (Race/CTS)")));
+               case "faults":       if (!mode) return CTX(_("SCO^faults"));       else LOG_HELP(strcat("^3", "faults", "             ^7", _("Number of faults committed")));
+               case "fckills":      if (!mode) return CTX(_("SCO^fckills"));      else LOG_HELP(strcat("^3", "fckills", "            ^7", _("Number of flag carrier kills")));
+               case "fps":          if (!mode) return CTX(_("SCO^fps"));          else LOG_HELP(strcat("^3", "fps", "                ^7", _("FPS")));
+               case "frags":        if (!mode) return CTX(_("SCO^frags"));        else LOG_HELP(strcat("^3", "frags", "              ^7", _("Number of kills minus suicides")));
+               case "goals":        if (!mode) return CTX(_("SCO^goals"));        else LOG_HELP(strcat("^3", "goals", "              ^7", _("Number of goals scored")));
+               case "kckills":      if (!mode) return CTX(_("SCO^kckills"));      else LOG_HELP(strcat("^3", "kckills", "            ^7", _("Number of keys carrier kills")));
+               case "kd":           if (!mode) return CTX(_("SCO^k/d"));          else LOG_HELP(strcat("^3", "kd", "                 ^7", _("The kill-death ratio")));
+               case "kdr":          if (!mode) return CTX(_("SCO^kdr"));          else LOG_HELP(strcat("^3", "kdr", "                ^7", _("The kill-death ratio")));
+               case "kdratio":      if (!mode) return CTX(_("SCO^kdratio"));      else LOG_HELP(strcat("^3", "kdratio", "            ^7", _("The kill-death ratio")));
+               case "kills":        if (!mode) return CTX(_("SCO^kills"));        else LOG_HELP(strcat("^3", "kills", "              ^7", _("Number of kills")));
+               case "laps":         if (!mode) return CTX(_("SCO^laps"));         else LOG_HELP(strcat("^3", "laps", "               ^7", _("Number of laps finished (Race/CTS)")));
+               case "lives":        if (!mode) return CTX(_("SCO^lives"));        else LOG_HELP(strcat("^3", "lives", "              ^7", _("Number of lives (LMS)")));
+               case "losses":       if (!mode) return CTX(_("SCO^losses"));       else LOG_HELP(strcat("^3", "losses", "             ^7", _("Number of times a key was lost")));
+               case "name":         if (!mode) return CTX(_("SCO^name"));         else LOG_HELP(strcat("^3", "name", "               ^7", _("Player name")));
+               case "nick":         if (!mode) return CTX(_("SCO^nick"));         else LOG_HELP(strcat("^3", "nick", "               ^7", _("Player name")));
+               case "objectives":   if (!mode) return CTX(_("SCO^objectives"));   else LOG_HELP(strcat("^3", "objectives", "         ^7", _("Number of objectives destroyed")));
+               case "pickups":      if (!mode) return CTX(_("SCO^pickups"));      else LOG_HELP(strcat("^3", "pickups", "            ^7", _("How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up")));
+               case "ping":         if (!mode) return CTX(_("SCO^ping"));         else LOG_HELP(strcat("^3", "ping", "               ^7", _("Ping time")));
+               case "pl":           if (!mode) return CTX(_("SCO^pl"));           else LOG_HELP(strcat("^3", "pl", "                 ^7", _("Packet loss")));
+               case "pushes":       if (!mode) return CTX(_("SCO^pushes"));       else LOG_HELP(strcat("^3", "pushes", "             ^7", _("Number of players pushed into void")));
+               case "rank":         if (!mode) return CTX(_("SCO^rank"));         else LOG_HELP(strcat("^3", "rank", "               ^7", _("Player rank")));
+               case "returns":      if (!mode) return CTX(_("SCO^returns"));      else LOG_HELP(strcat("^3", "returns", "            ^7", _("Number of flag returns")));
+               case "revivals":     if (!mode) return CTX(_("SCO^revivals"));     else LOG_HELP(strcat("^3", "revivals", "           ^7", _("Number of revivals")));
+               case "rounds":       if (!mode) return CTX(_("SCO^rounds won"));   else LOG_HELP(strcat("^3", "rounds", "             ^7", _("Number of rounds won")));
+               case "score":        if (!mode) return CTX(_("SCO^score"));        else LOG_HELP(strcat("^3", "score", "              ^7", _("Total score")));
+               case "suicides":     if (!mode) return CTX(_("SCO^suicides"));     else LOG_HELP(strcat("^3", "suicides", "           ^7", _("Number of suicides")));
+               case "sum":          if (!mode) return CTX(_("SCO^sum"));          else LOG_HELP(strcat("^3", "sum", "                ^7", _("Number of kills minus deaths")));
+               case "takes":        if (!mode) return CTX(_("SCO^takes"));        else LOG_HELP(strcat("^3", "takes", "              ^7", _("Number of domination points taken (Domination)")));
+               case "teamkills":    if (!mode) return CTX(_("SCO^teamkills"));    else LOG_HELP(strcat("^3", "teamkills", "          ^7", _("Number of teamkills")));
+               case "ticks":        if (!mode) return CTX(_("SCO^ticks"));        else LOG_HELP(strcat("^3", "ticks", "              ^7", _("Number of ticks (Domination)")));
+               case "time":         if (!mode) return CTX(_("SCO^time"));         else LOG_HELP(strcat("^3", "time", "               ^7", _("Total time raced (Race/CTS)")));
                default: return label;
        }
        return label;
@@@ -310,34 -325,34 +326,34 @@@ void Scoreboard_UpdateTeamPos(entity Te
  
  void Cmd_Scoreboard_Help()
  {
-       LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
-       LOG_INFO(_("Usage:"));
-       LOG_INFO("^2scoreboard_columns_set ^3default");
-       LOG_INFO(_("^2scoreboard_columns_set ^3field1 field2 ..."));
-       LOG_INFO(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
-       LOG_INFO(_("  ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
-       LOG_INFO(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
-       LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields."));
-       LOG_INFO(_("The following field names are recognized (case insensitive):"));
-       LOG_INFO("");
+       LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
+       LOG_HELP(_("Usage:"));
+       LOG_HELP("^2scoreboard_columns_set ^3default");
+       LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
+       LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
+       LOG_HELP(_("  ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
+       LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
+       LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
+       LOG_HELP(_("The following field names are recognized (case insensitive):"));
+       LOG_HELP("");
  
        PrintScoresLabels();
-       LOG_INFO("");
+       LOG_HELP("");
  
-       LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
+       LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
                "of game types, then a slash, to make the field show up only in these\n"
                "or in all but these game types. You can also specify 'all' as a\n"
                "field to show all fields available for the current game mode."));
-       LOG_INFO("");
+       LOG_HELP("");
  
-       LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
+       LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
                "include/exclude ALL teams/noteams game modes."));
-       LOG_INFO("");
+       LOG_HELP("");
  
-       LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
-       LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
+       LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
+       LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
                "right of the vertical bar aligned to the right."));
-       LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
+       LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
                        "other gamemodes except DM."));
  }
  
@@@ -673,7 -688,7 +689,7 @@@ string Scoreboard_GetField(entity pl, P
                                sbt_field_rgb = '1 1 1';
                                return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
                        }
-                       //sbt_field_rgb = HUD_Get_Num_Color(fps, 200);
+                       //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
                        sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
                        return ftos(fps);
                }
@@@ -1131,7 -1146,7 +1147,7 @@@ bool Scoreboard_WouldDraw(
                return true;
        else if (intermission == 2)
                return false;
-       else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !ISGAMETYPE(CTS)
+       else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
                && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
        {
                return true;
@@@ -1168,18 -1183,18 +1184,18 @@@ vector Scoreboard_AccuracyStats_Draw(ve
                }
                if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
                {
-                       if (((it.spawnflags & WEP_FLAG_HIDDEN) || (it.spawnflags & WEP_FLAG_MUTATORBLOCKED)))
+                       if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
                                ++nHidden;
                        else
                                ++disownedcnt;
                }
        });
  
-       int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
+       int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
        if (weapon_cnt <= 0) return pos;
  
        int rows = 1;
-       if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
+       if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
                rows = 2;
        int columnns = ceil(weapon_cnt / rows);
  
        return initial_pos + (end_pos - initial_pos) * scoreboard_acc_fade_alpha;
  }
  
 +vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
 +{
 +      float scoreboard_acc_fade_alpha_save = scoreboard_acc_fade_alpha; // debug
 +      scoreboard_acc_fade_alpha = 1; // debug: make Item Stats always visible
 +
 +      float initial_posx = pos.x;
 +      int disownedcnt = 0;
 +      FOREACH(Items, true, {
 +              int q = g_inventory.inv_items[it.m_id];
 +              //q = 1; // debug: display all items
 +              if (!q) ++disownedcnt;
 +      });
 +
 +      int n = Items_COUNT - disownedcnt;
 +      if (n <= 0) return pos;
 +
 +      int rows = (autocvar_hud_panel_scoreboard_accuracy_doublerows && n >= floor(Items_COUNT / 2)) ? 2 : 1;
 +      int columnns = ceil(n / rows);
 +
 +      float height = 40;
 +      float fontsize = height * 1/3;
 +      float item_height = height * 2/3;
 +
 +      drawstring(pos, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
 +      pos.y += 1.25 * hud_fontsize.y;
 +      if(panel.current_panel_bg != "0")
 +              pos.y += panel_bg_border;
 +
 +      panel_pos = pos;
 +      panel_size.y = height * rows;
 +      panel_size.y += panel_bg_padding * 2;
 +
 +      float panel_bg_alpha_save = panel_bg_alpha;
 +      panel_bg_alpha *= scoreboard_acc_fade_alpha;
 +      HUD_Panel_DrawBg();
 +      panel_bg_alpha = panel_bg_alpha_save;
 +
 +      vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
 +      if(panel.current_panel_bg != "0")
 +              end_pos.y += panel_bg_border * 2;
 +
 +      if(panel_bg_padding)
 +      {
 +              panel_pos += '1 1 0' * panel_bg_padding;
 +              panel_size -= '2 2 0' * panel_bg_padding;
 +      }
 +
 +      pos = panel_pos;
 +      vector tmp = panel_size;
 +
 +      float item_width = tmp.x / columnns / rows;
 +
 +      if (sbt_bg_alpha)
 +              drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
 +
 +      if(sbt_highlight)
 +      {
 +              // column highlighting
 +              for (int i = 0; i < columnns; ++i)
 +                      if ((i % 2) == 0)
 +                              drawfill(pos + '1 0 0' * item_width * rows * i, '0 1 0' * height * rows + '1 0 0' * item_width * rows, '0 0 0', panel_bg_alpha * 0.2, DRAWFLAG_NORMAL);
 +
 +              // row highlighting
 +              for (int i = 0; i < rows; ++i)
 +                      drawfill(pos + '0 1 0' * item_height + '0 1 0' * height * i, '1 0 0' * panel_size.x + '0 1 0' * fontsize, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
 +      }
 +
 +      if (rows == 2)
 +              pos.x += item_width / 2;
 +
 +      float oldposx = pos.x;
 +      vector tmpos = pos;
 +
 +      int column = 0;
 +      FOREACH(Items, true, {
 +              int n = g_inventory.inv_items[it.m_id];
 +              //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
 +              if (n <= 0) continue;
 +              drawpic_aspect_skin(tmpos, it.m_icon, '1 0 0' * item_width + '0 1 0' * item_height, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
 +              string s = ftos(n);
 +              float padding = (item_width - stringwidth(s, false, '1 0 0' * fontsize)) / 2; // center
 +              drawstring(tmpos + '1 0 0' * padding + '0 1 0' * item_height, s, '1 1 0' * fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
 +              tmpos.x += item_width * rows;
 +              pos.x += item_width * rows;
 +              if (rows == 2 && column == columnns - 1) {
 +                      tmpos.x = oldposx;
 +                      tmpos.y += height;
 +                      pos.y += height;
 +              }
 +              ++column;
 +      });
 +      pos.y += height;
 +      pos.y += 1.25 * hud_fontsize.y;
 +      pos.x = initial_posx;
 +
 +      panel_size.x += panel_bg_padding * 2; // restore initial width
 +
 +      scoreboard_acc_fade_alpha = scoreboard_acc_fade_alpha_save; // debug
 +      return pos;
 +}
 +
  vector MapStats_DrawKeyValue(vector pos, string key, string value) {
        float px = pos.x;
        pos.x += hud_fontsize.x * 0.25;
@@@ -1480,7 -1394,7 +1496,7 @@@ vector Scoreboard_MapStats_Draw(vector 
  }
  
  
- vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
+ vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
  {
        int i;
        RANKINGS_RECEIVED_CNT = 0;
        vector hl_rgb = rgb + '0.5 0.5 0.5';
  
        pos.y += hud_fontsize.y;
-       drawstring(pos + eX * panel_bg_padding, ((ISGAMETYPE(CTF)) ? _("Capture time rankings") : _("Rankings")), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
+       drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
        pos.y += 1.25 * hud_fontsize.y;
        if(panel.current_panel_bg != "0")
                pos.y += panel_bg_border;
@@@ -1590,7 -1504,7 +1606,7 @@@ float scoreboard_time
  bool have_weapon_stats;
  bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
  {
-       if (ISGAMETYPE(CTS) || ISGAMETYPE(RACE) || ISGAMETYPE(NEXBALL))
+       if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
                return false;
        if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
                return false;
@@@ -1706,14 -1620,14 +1722,14 @@@ void Scoreboard_Draw(
        draw_endBoldFont();
  
        // Game Info: Game Detail
-       float tl, fl, ll;
-       str = ""; // optionally "^7Limits: "
-       tl = STAT(TIMELIMIT);
-       fl = STAT(FRAGLIMIT);
-       ll = STAT(LEADLIMIT);
+       float tl = STAT(TIMELIMIT);
+       float fl = STAT(FRAGLIMIT);
+       float ll = STAT(LEADLIMIT);
+       float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
+       str = "";
        if(tl > 0)
                str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
-       if(!ISGAMETYPE(LMS))
+       if(!gametype.m_hidelimits)
        {
                if(fl > 0)
                {
                        {
                                str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
                                        (teamscores_label(ts_primary) == "score")   ? CTX(_("SCO^points")) :
-                                       (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
+                                       (teamscores_label(ts_primary) == "fastest") ? "" :
                                        TranslateScoresLabel(teamscores_label(ts_primary))));
                        }
                        else
                        {
                                str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
                                        (scores_label(ps_primary) == "score")   ? CTX(_("SCO^points")) :
-                                       (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
+                                       (scores_label(ps_primary) == "fastest") ? "" :
                                        TranslateScoresLabel(scores_label(ps_primary))));
                        }
                }
                if(ll > 0)
                {
                        if(tl > 0 || fl > 0)
-                               str = strcat(str, "^7 / "); // delimiter
+                       {
+                               // delimiter
+                               if (ll_and_fl && fl > 0)
+                                       str = strcat(str, "^7 & ");
+                               else
+                                       str = strcat(str, "^7 / ");
+                       }
                        if(teamplay)
                        {
                                str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
                                        (teamscores_label(ts_primary) == "score")   ? CTX(_("SCO^points")) :
-                                       (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
+                                       (teamscores_label(ts_primary) == "fastest") ? "" :
                                        TranslateScoresLabel(teamscores_label(ts_primary))));
                        }
                        else
                        {
                                str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
                                        (scores_label(ps_primary) == "score")   ? CTX(_("SCO^points")) :
-                                       (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
+                                       (scores_label(ps_primary) == "fastest") ? "" :
                                        TranslateScoresLabel(scores_label(ps_primary))));
                        }
                }
  
        if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
                pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
 +      pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
  
-       if(ISGAMETYPE(CTS) || ISGAMETYPE(RACE) || (autocvar_hud_panel_scoreboard_ctf_leaderboard && ISGAMETYPE(CTF) && STAT(CTF_SHOWLEADERBOARD))) {
+       if(MUTATOR_CALLHOOK(ShowRankings)) {
+               string ranktitle = M_ARGV(0, string);
                if(race_speedaward) {
                        drawcolorcodedstring(pos, sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, race_speedaward_unit, ColorTranslateRGB(race_speedaward_holder)), hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
                        pos.y += 1.25 * hud_fontsize.y;
                        drawcolorcodedstring(pos, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest, race_speedaward_alltimebest_unit, ColorTranslateRGB(race_speedaward_alltimebest_holder)), hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
                        pos.y += 1.25 * hud_fontsize.y;
                }
-               pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
+               pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
        }
  
        pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
index ed6c7355d2232b5c561b47be4efd08ed7fe442b3,c47be669978bb90b00afa5724297fc7d21c48544..cc17571ecc91bee3d94f401d7fec2a12457a29aa
@@@ -5,19 -5,33 +5,19 @@@
  #ifdef GAMEQC
  CLASS(Inventory, Object)
      /** Stores counts of items, the id being the index */
 -    ATTRIBARRAY(Inventory, inv_items, int, REGISTRY_MAX(Items));
 +    ATTRIBARRAY(Inventory, inv_items, int, Items_MAX);
-     /** Previous state */
-     ATTRIB(Inventory, inventory, Inventory);
  ENDCLASS(Inventory)
  
  /** Player inventory */
  .Inventory inventory;
+ /** Player inventory storage (holds previous state) */
+ .Inventory inventory_store;
  
  REGISTER_NET_LINKED(ENT_CLIENT_INVENTORY)
  
 -const int Inventory_groups_minor = 8; // must be a multiple of 8 (one byte) to optimize bandwidth usage
 -const int Inventory_groups_major = 4; // must be >= ceil(REGISTRY_COUNT(Items) / Inventory_groups_minor)
 -#endif
 +const int Inventory_groups_major = 16;
 +const int Inventory_groups_minor = 8; // ceil(Items_MAX / Inventory_groups_major)
  
 -// no need to perform these checks on both server and client
 -#ifdef CSQC
 -STATIC_INIT(Inventory)
 -{
 -      if (Inventory_groups_minor / 8 != floor(Inventory_groups_minor / 8))
 -              error("Inventory_groups_minor is not a multiple of 8.");
 -      int min_major_value = ceil(REGISTRY_COUNT(Items) / Inventory_groups_minor);
 -      if (Inventory_groups_major < min_major_value)
 -              error(sprintf("Inventory_groups_major can not be < %d.", min_major_value));
 -}
 -#endif
 -
 -#ifdef SVQC
  #define G_MAJOR(id) (floor((id) / Inventory_groups_minor))
  #define G_MINOR(id) ((id) % Inventory_groups_minor)
  #endif
@@@ -28,21 -42,21 +28,21 @@@ NET_HANDLE(ENT_CLIENT_INVENTORY, bool i
  {
      make_pure(this);
      g_inventory = this;
 -    const int majorBits = Readbits(Inventory_groups_major);
 +    const int majorBits = ReadShort();
      for (int i = 0; i < Inventory_groups_major; ++i) {
          if (!(majorBits & BIT(i))) {
              continue;
          }
 -        const int minorBits = Readbits(Inventory_groups_minor);
 +        const int minorBits = ReadByte();
          for (int j = 0; j < Inventory_groups_minor; ++j) {
              if (!(minorBits & BIT(j))) {
                  continue;
              }
 -            const GameItem it = REGISTRY_GET(Items, Inventory_groups_minor * i + j);
 +            const GameItem it = Items_from(Inventory_groups_minor * i + j);
              .int fld = inv_items[it.m_id];
              int prev = this.(fld);
              int next = this.(fld) = ReadByte();
 -            LOG_DEBUGF("%s: %.0f -> %.0f", it.m_name, prev, next);
 +            LOG_TRACEF("%s: %.0f -> %.0f", it.m_name, prev, next);
          }
      }
      return true;
@@@ -51,7 -65,7 +51,7 @@@
  
  #ifdef SVQC
  int minorBitsArr[Inventory_groups_major];
- void Inventory_Write(Inventory data)
+ void Inventory_Write(Inventory data, Inventory store)
  {
      if (!data) {
          WriteShort(MSG_ENTITY, 0);
      int majorBits = 0;
      FOREACH(Items, true, {
          .int fld = inv_items[it.m_id];
-         const bool changed = data.inventory.(fld) != data.(fld);
+         const bool changed = store.(fld) != data.(fld);
+         store.(fld) = data.(fld);
          if (changed) {
                        int maj = G_MAJOR(it.m_id);
                        majorBits = BITSET(majorBits, BIT(maj), true);
                        minorBitsArr[maj] = BITSET(minorBitsArr[maj], BIT(G_MINOR(it.m_id)), true);
          }
      });
 +    WriteShort(MSG_ENTITY, majorBits);
  
 -      Writebits(MSG_ENTITY, majorBits, Inventory_groups_major);
        for (int i = 0; i < Inventory_groups_major; ++i)
        {
                if (!(majorBits & BIT(i)))
                        continue;
  
                const int minorBits = minorBitsArr[i];
 -              Writebits(MSG_ENTITY, minorBits, Inventory_groups_minor);
 +              WriteByte(MSG_ENTITY, minorBits);
                for (int j = 0; j < Inventory_groups_minor; ++j)
                {
                        if (!(minorBits & BIT(j)))
                                continue;
  
 -                      const entity it = REGISTRY_GET(Items, Inventory_groups_minor * i + j);
 +                      const entity it = Items_from(Inventory_groups_minor * i + j);
                        WriteByte(MSG_ENTITY, data.inv_items[it.m_id]);
                }
        }
@@@ -102,7 -117,7 +103,7 @@@ bool Inventory_Send(Inventory this, Cli
      TC(Inventory, this);
      WriteHeader(MSG_ENTITY, ENT_CLIENT_INVENTORY);
      TC(PlayerState, this.owner);
-     Inventory_Write(this);
+     Inventory_Write(this, to.inventory_store);
      return true;
  }
  
@@@ -114,11 -129,13 +115,13 @@@ bool Inventory_customize(entity this, e
  
  void Inventory_new(PlayerState this)
  {
-     Inventory inv = NEW(Inventory), bak = NEW(Inventory);
-     inv.inventory = bak;
+     Inventory inv = NEW(Inventory);
      setcefc(inv, Inventory_customize);
      Net_LinkEntity((inv.owner = this).inventory = inv, false, 0, Inventory_Send);
  }
--void Inventory_delete(entity e) { delete(e.inventory.inventory); delete(e.inventory); }
++void Inventory_delete(entity e) { delete(e.inventory); }
  void Inventory_update(entity e) { e.inventory.SendFlags = 0xFFFFFF; }
+ void InventoryStorage_attach(entity e) { e.inventory_store = NEW(Inventory); e.inventory_store.drawonlytoclient = e; }
+ void InventoryStorage_detach(entity e) { delete(e.inventory_store); }
  #endif