]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into Juhu/scoreboard-strafe
authorJuhu <5894800-Juhu_@users.noreply.gitlab.com>
Sat, 20 Mar 2021 15:05:30 +0000 (16:05 +0100)
committerJuhu <5894800-Juhu_@users.noreply.gitlab.com>
Sat, 20 Mar 2021 15:15:32 +0000 (16:15 +0100)
1  2 
.gitlab-ci.yml
qcsrc/client/hud/panel/scoreboard.qc
qcsrc/common/gamemodes/gamemode/cts/sv_cts.qc

diff --combined .gitlab-ci.yml
index ef8d0847b0865cd4daf4de75d261d24e71bfd488,1fc76cf43efe20f2fc2429744e4efa8a3601060d..3010a5deb77a54fe5cd0600d6176ed827490842a
@@@ -1,81 -1,90 +1,90 @@@
- before_script:
-   - ln -s $PWD data/xonotic-data.pk3dir
-   - git clone --depth=1 --branch=master https://gitlab.com/xonotic/gmqcc.git gmqcc
-   - cd gmqcc && make -j $(nproc) && export QCC="$PWD/gmqcc"
-   - cd ..
- report_cloc:
-   stage: test
-   script:
-     - cloc --force-lang-def=qcsrc/tools/cloc.txt --sql 1 --sql-project xonotic qcsrc | sqlite3 code.db
-     - sqlite3 code.db 'select file,nCode from t where nCode > 1000 order by nBlank+nComment+nCode desc'
- test_compilation_units:
-   stage: test
-   script:
-     - ./qcsrc/tools/compilationunits.sh
- test_sv_game:
-   stage: test
-   script:
-     - git clone --depth=1 --branch=div0-stable https://gitlab.com/xonotic/darkplaces.git darkplaces
-     - cd darkplaces && make sv-debug -j $(nproc) && export ENGINE="$PWD/darkplaces-dedicated -xonotic"
-     - cd ..
-     - mkdir -p data/maps
-     - wget -O data/stormkeep.pk3 http://beta.xonotic.org/autobuild-bsp/latest/stormkeep.pk3
-     - wget -O data/maps/stormkeep.mapinfo https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.mapinfo
-     - wget -O data/maps/stormkeep.waypoints https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints
-     - wget -O data/maps/stormkeep.waypoints.cache https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints.cache
-     - make
-     - EXPECT=d357b667dc459574d1b4ae1a195d3d1f
-     - HASH=$(${ENGINE} -noconfig -nohome +exec serverbench.cfg
-       | tee /dev/stderr
-       | grep '^:'
-       | grep -v '^:gamestart:'
-       | grep -v '^:anticheat:'
-       | md5sum | awk '{ print $1 }')
-     - echo 'expected:' $EXPECT
-     - echo '  actual:' $HASH
-     - test "$HASH" == "$EXPECT"
-     - exit $?
- test_sv_unit:
-   stage: test
-   script:
-     - git clone --depth=1 --branch=div0-stable https://gitlab.com/xonotic/darkplaces.git darkplaces
-     - cd darkplaces && make sv-debug -j $(nproc) && export ENGINE="$PWD/darkplaces-dedicated -xonotic"
-     - cd ..
-     - mkdir maps && wget -O maps/gitlab-ci.bsp https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/_init/_init.bsp
-     - make
-     - while read line; do
-         echo $line;
-         if [[ $line == "All tests OK" ]]; then exit 0; fi;
-       done < <(${ENGINE} +developer 1 +map gitlab-ci +sv_cmd runtest +exit)
-     - exit 1
- # NOTE: The generated docs are incomplete - they don't contain code behind SVQC CSQC MENUQC GAMEQC ifdefs.
- # With them added to PREDEFINED, it would take over half an hour to generate the docs and even then
- # they might not be complete. Doxygen doesn't handle #elif and might not understand some QC definitions.
- doxygen:  # rename to 'pages' when gitlab.com allows pages to exceed 100MiB
-   stage: deploy
-   script:
-     - cd qcsrc && doxygen
-     - mv html ../public
-     - for i in {0..0}; do eval $(printf "echo \$id_rsa_%02d\n" $i) >> ~/.ssh/id_rsa_base64; done
-     - base64 --decode ~/.ssh/id_rsa_base64 > ~/.ssh/id_rsa
-     - chmod 600 ~/.ssh/id_rsa
-     - echo -e "Host *\n\tStrictHostKeyChecking no\n\tLogLevel ERROR\n" >> ~/.ssh/config
-     - git config --global user.name "Gitlab CI"
-     - git config --global user.email "<>"
-     - git clone --single-branch --depth 1 ${DEPLOY_HOST}:${DEPLOY_REPO} ~/deploy_
-     - mkdir ~/deploy && mv ~/deploy_/.git ~/deploy && rm -r ~/deploy_
-     - cp -r ../public/* ~/deploy
-     - cd ~/deploy && git add -A . && git commit -m "Deploy ${CI_BUILD_REF}" && git push origin gh-pages
-   artifacts:
-     paths:
-       - public
-   only:
-     - master
+ workflow:\r
+   rules:\r
+     - if: $CI_COMMIT_MESSAGE =~ /Transifex autosync/\r
+       when: never\r
+     - when: always\r
\r
+ before_script:\r
+   - ln -s $PWD data/xonotic-data.pk3dir\r
\r
+   - git clone --depth=1 --branch=main https://gitlab.com/xonotic/gmqcc.git gmqcc\r
+   - cd gmqcc && make -j $(nproc) && export QCC="$PWD/gmqcc"\r
+   - cd ..\r
\r
+ test_compilation_units:\r
+   rules:\r
+     - changes:\r
+       - qcsrc/**/*\r
+   stage: test\r
+   script:\r
+     - ./qcsrc/tools/compilationunits.sh\r
\r
+ test_sv_game:\r
+   stage: test\r
+   script:\r
+     - git clone --depth=1 --branch=div0-stable https://gitlab.com/xonotic/darkplaces.git darkplaces\r
+     - cd darkplaces && make sv-debug -j $(nproc) && export ENGINE="$PWD/darkplaces-dedicated -xonotic"\r
+     - cd ..\r
\r
+     - mkdir -p data/maps\r
+     - wget -O data/stormkeep.pk3 http://beta.xonotic.org/autobuild-bsp/latest/stormkeep.pk3\r
+     - wget -O data/maps/stormkeep.mapinfo https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.mapinfo\r
+     - wget -O data/maps/stormkeep.waypoints https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints\r
+     - wget -O data/maps/stormkeep.waypoints.cache https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints.cache\r
+     - make\r
 -    - EXPECT=570561a503853d1224450fbdfb8b3315\r
++    - EXPECT=ae4d27dd465e976cd5afa35365eea0d7\r
+     - HASH=$(${ENGINE} -noconfig -nohome +timestamps 1 +exec serverbench.cfg\r
+       | tee /dev/stderr\r
+       | sed -e 's,^\[[^]]*\] ,,'\r
+       | grep '^:'\r
+       | grep -v '^:gamestart:'\r
+       | grep -v '^:anticheat:'\r
+       | md5sum | awk '{ print $1 }')\r
+     - echo 'expected:' $EXPECT\r
+     - echo '  actual:' $HASH\r
+     - test "$HASH" == "$EXPECT"\r
+     - exit $?\r
\r
+ test_sv_unit:\r
+   stage: test\r
+   script:\r
+     - git clone --depth=1 --branch=div0-stable https://gitlab.com/xonotic/darkplaces.git darkplaces\r
+     - cd darkplaces && make sv-debug -j $(nproc) && export ENGINE="$PWD/darkplaces-dedicated -xonotic"\r
+     - cd ..\r
\r
+     - mkdir maps && wget -O maps/gitlab-ci.bsp https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/_init/_init.bsp\r
+     - make\r
+     - while read line; do\r
+         echo $line;\r
+         if [[ $line == "All tests OK" ]]; then exit 0; fi;\r
+       done < <(${ENGINE} +developer 1 +map gitlab-ci +sv_cmd runtest +exit)\r
+     - exit 1\r
\r
+ # NOTE: The generated docs are incomplete - they don't contain code behind SVQC CSQC MENUQC GAMEQC ifdefs.\r
+ # With them added to PREDEFINED, it would take over half an hour to generate the docs and even then\r
+ # they might not be complete. Doxygen doesn't handle #elif and might not understand some QC definitions.\r
+ doxygen:  # rename to 'pages' when gitlab.com allows pages to exceed 100MiB\r
+   before_script:\r
+     - ln -s $PWD data/xonotic-data.pk3dir # is this needed?\r
+     - apt-get update\r
+     - apt-get -y install doxygen graphviz\r
+   stage: deploy\r
+   script:\r
+     - cd qcsrc && doxygen\r
+     - mv html ../public\r
+     - mkdir -p ~/.ssh\r
+     - for i in {0..0}; do eval $(printf "echo \$id_rsa_%02d\n" $i) >> ~/.ssh/id_rsa_base64; done\r
+     - base64 --decode ~/.ssh/id_rsa_base64 > ~/.ssh/id_rsa\r
+     - chmod 600 ~/.ssh/id_rsa\r
+     - echo -e "Host *\n\tStrictHostKeyChecking no\n\tLogLevel ERROR\n" >> ~/.ssh/config\r
+     - git config --global user.name "Gitlab CI"\r
+     - git config --global user.email "<>"\r
+     - git clone --single-branch --depth 1 ${DEPLOY_HOST}:${DEPLOY_REPO} ~/deploy_\r
+     - mkdir ~/deploy && mv ~/deploy_/.git ~/deploy && rm -r ~/deploy_\r
+     - cp -r ../public/* ~/deploy\r
+     - cd ~/deploy && git add -A . && git commit -m "Deploy ${CI_BUILD_REF}" && git push origin gh-pages\r
+   artifacts:\r
+     paths:\r
+       - public\r
+   only:\r
+     - master\r
index 77049b0391800c5d506e3fda7ee8f8546dc001f9,39deff0d1c36e202696428237d93589679eb4195..f60210da5a0c06dc1a80318233b16acd8990ac33
@@@ -1,19 -1,19 +1,19 @@@
  #include "scoreboard.qh"
  
- #include <client/autocvars.qh>
  #include <client/draw.qh>
- #include <client/main.qh>
+ #include <client/hud/panel/chat.qh>
+ #include <client/hud/panel/quickmenu.qh>
  #include <client/hud/panel/racetimer.qh>
  #include <client/hud/panel/weapons.qh>
- #include "quickmenu.qh"
- #include <common/ent_cs.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)
  
@@@ -30,6 -30,7 +30,7 @@@ void Scoreboard_Draw_Export(int fh
        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_table_highlight_alpha_eliminated");
        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");
@@@ -52,6 -53,7 +53,7 @@@ float sbt_fg_alpha_self
  bool sbt_highlight;
  float sbt_highlight_alpha;
  float sbt_highlight_alpha_self;
+ float sbt_highlight_alpha_eliminated;
  
  // provide basic panel cvars to old clients
  // TODO remove them after a future release (0.8.2+)
@@@ -74,6 -76,7 +76,7 @@@ float autocvar_hud_panel_scoreboard_tab
  bool autocvar_hud_panel_scoreboard_table_highlight = true;
  float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
  float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
+ float autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated = 0.6;
  float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
  float autocvar_hud_panel_scoreboard_namesize = 15;
  float autocvar_hud_panel_scoreboard_team_size_position = 0;
@@@ -84,6 -87,12 +87,12 @@@ 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_itemstats = true;
+ bool autocvar_hud_panel_scoreboard_itemstats_doublerows = false;
+ bool autocvar_hud_panel_scoreboard_itemstats_filter = true;
+ float autocvar_hud_panel_scoreboard_itemstats_showdelay = 2.2; // slightly more delayed than accuracy
+ float autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos = 0.75;
  bool autocvar_hud_panel_scoreboard_dynamichud = false;
  
  float autocvar_hud_panel_scoreboard_maxheight = 0.6;
@@@ -91,6 -100,9 +100,9 @@@ bool autocvar_hud_panel_scoreboard_othe
  bool autocvar_hud_panel_scoreboard_spectators_showping = true;
  bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
  float autocvar_hud_panel_scoreboard_minwidth = 0.4;
+ bool autocvar_hud_panel_scoreboard_playerid = false;
+ string autocvar_hud_panel_scoreboard_playerid_prefix = "#";
+ string autocvar_hud_panel_scoreboard_playerid_suffix = " ";
  
  // mode 0: returns translated label
  // mode 1: prints name and description of all the labels
@@@ -137,7 -149,6 +149,7 @@@ string Label_getInfo(string label, int 
                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 "strafe":       if (!mode) return CTX(_("SCO^strafe"));       else LOG_HELP(strcat("^3", "strafe", "             ^7", _("Strafe efficiency (CTS)")));
                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)")));
@@@ -374,7 -385,7 +386,7 @@@ void Cmd_Scoreboard_Help(
  " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
  " +lms/lives +lms/rank" \
  " +kh/kckills +kh/losses +kh/caps" \
 -" ?+rc/laps ?+rc/time +rc,cts/fastest" \
 +" ?+rc/laps ?+rc/time ?+cts/strafe +rc,cts/fastest" \
  " +as/objectives +nb/faults +nb/goals" \
  " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
  " +dom/ticks +dom/takes" \
@@@ -570,6 -581,13 +582,13 @@@ LABEL(found
        sbt_field[sbt_num_fields] = SP_END;
  }
  
+ string Scoreboard_AddPlayerId(string pl_name, entity pl)
+ {
+       string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
+       string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
+       return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
+ }
  // MOVEUP::
  vector sbt_field_rgb;
  string sbt_field_icon0;
@@@ -638,7 -656,10 +657,10 @@@ string Scoreboard_GetField(entity pl, P
                        return str;
  
                case SP_NAME:
-                       return Scoreboard_GetName(pl);
+                       str = Scoreboard_GetName(pl);
+                       if (autocvar_hud_panel_scoreboard_playerid)
+                               str = Scoreboard_AddPlayerId(str, pl);
+                       return str;
  
                case SP_FRAGS:
                        f = pl.(scores(SP_KILLS));
                case SP_DMG: case SP_DMGTAKEN:
                        return sprintf("%.1f k", pl.(scores(field)) / 1000);
  
 +              case SP_CTS_STRAFE:
 +              {
 +                      float strafe_efficiency = pl.(scores(field)) / 10000;
 +                      if(strafe_efficiency < -1) return "";
 +                      sbt_field_rgb = '1 1 1' - (strafe_efficiency > 0 ? '1 0 1' : '0 1 1') * fabs(strafe_efficiency);
 +                      return sprintf("%.2f%%", strafe_efficiency * 100);
 +              }
 +
                default: case SP_SCORE:
                        tmp = pl.(scores(field));
                        f = scores_flags(field);
@@@ -867,7 -880,7 +889,7 @@@ void Scoreboard_DrawItem(vector item_po
        vector pos = item_pos;
        // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
        if (is_self)
-               drawstring(pos+eX*(panel_size.x+.5*hud_fontsize.x)+eY, "\xE2\x97\x80", vec2(hud_fontsize.x, hud_fontsize.y), rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
+               drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
  
        pos.x += hud_fontsize.x * 0.5;
        pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
        }
  
        if(pl.eliminated)
-               drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
+               drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
  }
  
  vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
                else if(autocvar_hud_panel_scoreboard_others_showscore)
                        field = Scoreboard_GetField(pl, SP_SCORE);
  
-               string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
+               string str = entcs_GetName(pl.sv_entnum);
+               if (autocvar_hud_panel_scoreboard_playerid)
+                       str = Scoreboard_AddPlayerId(str, pl);
+               str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
                float column_width = stringwidth(str, true, hud_fontsize);
                if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
                {
                {
                        h_size.x = column_width + hud_fontsize.x * 0.25;
                        h_size.y = hud_fontsize.y;
-                       drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
+                       drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
                }
                pos.x += column_width;
                pos.x += hud_fontsize.x;
@@@ -1079,7 -1095,7 +1104,7 @@@ vector Scoreboard_MakeTable(vector pos
        panel_size.y += panel_bg_padding * 2;
        HUD_Panel_DrawBg();
  
-       vector end_pos = panel_pos + eY * (panel_size.y + 0.5* hud_fontsize.y);
+       vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
        if(panel.current_panel_bg != "0")
                end_pos.y += panel_bg_border * 2;
  
@@@ -1161,7 -1177,7 +1186,7 @@@ bool Scoreboard_WouldDraw(
        {
                return true;
        }
-       else if (scoreboard_showscores_force)
+       else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
                return true;
        return false;
  }
  float average_accuracy;
  vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
  {
-       if (frametime)
-       {
-               if (scoreboard_fade_alpha == 1)
-                       scoreboard_acc_fade_alpha = min(1, scoreboard_acc_fade_alpha + frametime * 10);
-               else
-                       scoreboard_acc_fade_alpha = 1; // sync fading with the scoreboard
-       }
-       vector initial_pos = pos;
+       scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
  
        WepSet weapons_stat = WepSet_GetFromStat();
        WepSet weapons_inmap = WepSet_GetFromStat_InMap();
        HUD_Panel_DrawBg();
        panel_bg_alpha = panel_bg_alpha_save;
  
-       vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
+       vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
        if(panel.current_panel_bg != "0")
                end_pos.y += panel_bg_border * 2;
  
  
        panel_size.x += panel_bg_padding * 2; // restore initial width
  
-       if (scoreboard_acc_fade_alpha == 1)
-               return end_pos;
-       return initial_pos + (end_pos - initial_pos) * scoreboard_acc_fade_alpha;
+       return end_pos;
+ }
+ .bool uninteresting;
+ STATIC_INIT(default_order_items_label)
+ {
+       IL_EACH(default_order_items, true, {
+               if(!(it.instanceOfPowerup
+                       || it == ITEM_HealthMega || it == ITEM_HealthBig
+                       || it == ITEM_ArmorMega || it == ITEM_ArmorBig
+                       ))
+               {
+                       it.uninteresting = true;
+               }
+       });
+ }
+ vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
+ {
+       scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
+       int disowned_cnt = 0;
+       int uninteresting_cnt = 0;
+       IL_EACH(default_order_items, true, {
+               int q = g_inventory.inv_items[it.m_id];
+               //q = 1; // debug: display all items
+               if (autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting)
+                       ++uninteresting_cnt;
+               else if (!q)
+                       ++disowned_cnt;
+       });
+       int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
+       int n = items_cnt - disowned_cnt;
+       if (n <= 0) return pos;
+       int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
+       int columnns = max(6, ceil(n / rows));
+       float height = 40;
+       float fontsize = height * 1/3;
+       float item_height = height * 2/3;
+       drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_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_itemstats_fade_alpha;
+       HUD_Panel_DrawBg();
+       panel_bg_alpha = panel_bg_alpha_save;
+       vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * 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_itemstats_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', sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, 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, rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
+       }
+       if (rows == 2)
+               pos.x += item_width / 2;
+       float oldposx = pos.x;
+       vector tmpos = pos;
+       int column = 0;
+       IL_EACH(default_order_items, !(autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting), {
+               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 * scoreboard_itemstats_fade_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 * scoreboard_itemstats_fade_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;
+       });
+       panel_size.x += panel_bg_padding * 2; // restore initial width
+       return end_pos;
  }
  
  vector MapStats_DrawKeyValue(vector pos, string key, string value) {
@@@ -1369,7 -1489,7 +1498,7 @@@ vector Scoreboard_MapStats_Draw(vector 
        panel_size.y += panel_bg_padding * 2;
        HUD_Panel_DrawBg();
  
-       vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
+       vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
        if(panel.current_panel_bg != "0")
                end_pos.y += panel_bg_border * 2;
  
@@@ -1455,7 -1575,7 +1584,7 @@@ vector Scoreboard_Rankings_Draw(vector 
  
        HUD_Panel_DrawBg();
  
-       vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
+       vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
        if(panel.current_panel_bg != "0")
                end_pos.y += panel_bg_border * 2;
  
@@@ -1543,6 -1663,42 +1672,42 @@@ bool Scoreboard_AccuracyStats_WouldDraw
        return true;
  }
  
+ bool have_item_stats;
+ bool Scoreboard_ItemStats_WouldDraw(float ypos)
+ {
+       if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
+               return false;
+       if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
+               return false;
+       if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
+               && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
+               && !intermission)
+       {
+               return false;
+       }
+       if (!have_item_stats)
+       {
+               IL_EACH(default_order_items, true, {
+                       if (!(autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting))
+                       {
+                               int q = g_inventory.inv_items[it.m_id];
+                               //q = 1; // debug: display all items
+                               if (q)
+                               {
+                                       have_item_stats = true;
+                                       break;
+                               }
+                       }
+               });
+               if (!have_item_stats)
+                       return false;
+       }
+       return true;
+ }
  void Scoreboard_Draw()
  {
        if(!autocvar__hud_configure)
  
                // frametime checks allow to toggle the scoreboard even when the game is paused
                if(scoreboard_active) {
-                       if (scoreboard_fade_alpha < 1)
+                       if (scoreboard_fade_alpha == 0)
                                scoreboard_time = time;
                        if(hud_configure_menu_open == 1)
                                scoreboard_fade_alpha = 1;
                if (!scoreboard_fade_alpha)
                {
                        scoreboard_acc_fade_alpha = 0;
+                       scoreboard_itemstats_fade_alpha = 0;
                        return;
                }
        }
        sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
        sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
        sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
+       sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
        sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
        sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
  
  
        Scoreboard_UpdatePlayerTeams();
  
+       float initial_pos_y = panel_pos.y;
        vector pos = panel_pos;
        entity pl, tm;
        string str;
  
        if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
                pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
+       if (Scoreboard_ItemStats_WouldDraw(pos.y))
+               pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
  
        if(MUTATOR_CALLHOOK(ShowRankings)) {
                string ranktitle = M_ARGV(0, string);
                drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
        }
  
-       scoreboard_bottom = pos.y + 2 * hud_fontsize.y;
+       pos.y += 2 * hud_fontsize.y;
+       if (scoreboard_fade_alpha < 1)
+               scoreboard_bottom = initial_pos_y + (pos.y - initial_pos_y) * scoreboard_fade_alpha;
+       else if (pos.y != scoreboard_bottom)
+       {
+               if (pos.y > scoreboard_bottom)
+                       scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - initial_pos_y));
+               else
+                       scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - initial_pos_y));
+       }
  }
index f28c5613e6e68b646d7defdf57c7938c3b257ad5,458465faaa260587a60563407fa72630d228c21e..31988cb1086bf3bcdb85943ddaa5d4d1835d1d36
@@@ -56,7 -56,6 +56,7 @@@ void cts_ScoreRules(
      GameRules_score_enabled(false);
      GameRules_scoring(0, 0, 0, {
          if (g_race_qualifying) {
 +            field(SP_CTS_STRAFE, "strafe", 0);
              field(SP_RACE_FASTEST, "fastest", SFL_SORT_PRIO_PRIMARY | SFL_LOWER_IS_BETTER | SFL_TIME);
          } else {
              field(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY);
@@@ -141,7 -140,6 +141,7 @@@ MUTATOR_HOOKFUNCTION(cts, PlayerPhysics
                                CS(player).movement_y = -M_SQRT1_2 * wishspeed;
                }
        }
 +      player.strafe_efficiency_sum += calculate_strafe_efficiency(player, CS(player).movement);
  }
  
  MUTATOR_HOOKFUNCTION(cts, reset_map_global)
        Score_NicePrint(NULL);
  
        race_ClearRecords();
-       PlayerScore_Sort(race_place, 0, 1, 0);
+       PlayerScore_Sort(race_place, 0, true, false);
  
        FOREACH_CLIENT(true, {
 +              it.strafe_efficiency_best = -2;
 +              PlayerScore_Set(it, SP_CTS_STRAFE, -20000);
 +
                if(it.race_place)
                {
                        s = GameRules_scoring_add(it, RACE_FASTEST, 0);
@@@ -183,31 -178,8 +183,11 @@@ MUTATOR_HOOKFUNCTION(cts, ClientConnect
  
        race_PreparePlayer(player);
        player.race_checkpoint = -1;
++      player.strafe_efficiency_sum = player.strafe_efficiency_tics = 0;
++      player.strafe_efficiency_best = -2;
++      PlayerScore_Set(player, SP_CTS_STRAFE, -20000);
  
-       if(IS_REAL_CLIENT(player))
-       {
-               string rr = CTS_RECORD;
-               msg_entity = player;
-               race_send_recordtime(MSG_ONE);
-               race_send_speedaward(MSG_ONE);
-               speedaward_alltimebest = stof(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed")));
-               speedaward_alltimebest_holder = uid2name(db_get(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp")));
-               race_send_speedaward_alltimebest(MSG_ONE);
-               float i;
-               int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt);
-               race_send_rankings_cnt(MSG_ONE);
-               for (i = 1; i <= m; ++i)
-               {
-                       race_SendRankings(i, 0, 0, MSG_ONE);
-               }
-               player.strafe_efficiency_sum = player.strafe_efficiency_tics = 0;
-               player.strafe_efficiency_best = -2;
-               PlayerScore_Set(player, SP_CTS_STRAFE, -20000);
-       }
+       race_SendAll(player, false);
  }
  
  MUTATOR_HOOKFUNCTION(cts, AbortSpeedrun)
@@@ -270,8 -242,6 +250,8 @@@ MUTATOR_HOOKFUNCTION(cts, PlayerDies
        frag_target.respawn_flags |= RESPAWN_FORCE;
        race_AbandonRaceCheck(frag_target);
  
 +      frag_target.strafe_efficiency_sum = frag_target.strafe_efficiency_tics = 0;
 +
        if(autocvar_g_cts_removeprojectiles)
        {
                IL_EACH(g_projectiles, it.owner == frag_target && (it.flags & FL_PROJECTILE),
@@@ -293,42 -263,8 +273,8 @@@ MUTATOR_HOOKFUNCTION(cts, GetPressedKey
  {
        entity player = M_ARGV(0, entity);
  
-       if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1)
-       {
-               if (!player.stored_netname)
-                       player.stored_netname = strzone(uid2name(player.crypto_idfp));
-               if(player.stored_netname != player.netname)
-               {
-                       db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
-                       strcpy(player.stored_netname, player.netname);
-               }
-       }
-       if (!IS_OBSERVER(player))
-       {
-               if(vdist(player.velocity - player.velocity_z * '0 0 1', >, speedaward_speed))
-               {
-                       speedaward_speed = vlen(player.velocity - player.velocity_z * '0 0 1');
-                       speedaward_holder = player.netname;
-                       speedaward_uid = player.crypto_idfp;
-                       speedaward_lastupdate = time;
-               }
-               if (speedaward_speed > speedaward_lastsent && time - speedaward_lastupdate > 1)
-               {
-                       string rr = CTS_RECORD;
-                       race_send_speedaward(MSG_ALL);
-                       speedaward_lastsent = speedaward_speed;
-                       if (speedaward_speed > speedaward_alltimebest && speedaward_uid != "")
-                       {
-                               speedaward_alltimebest = speedaward_speed;
-                               speedaward_alltimebest_holder = speedaward_holder;
-                               speedaward_alltimebest_uid = speedaward_uid;
-                               db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/speed"), ftos(speedaward_alltimebest));
-                               db_put(ServerProgsDB, strcat(GetMapname(), rr, "speed/crypto_idfp"), speedaward_alltimebest_uid);
-                               race_send_speedaward_alltimebest(MSG_ALL);
-                       }
-               }
-       }
+       race_checkAndWriteName(player);
+       race_SpeedAwardFrame(player);
  }
  
  MUTATOR_HOOKFUNCTION(cts, ForbidThrowCurrentWeapon)
@@@ -397,13 -333,6 +343,13 @@@ MUTATOR_HOOKFUNCTION(cts, ClientKill
  MUTATOR_HOOKFUNCTION(cts, Race_FinalCheckpoint)
  {
        entity player = M_ARGV(0, entity);
 +      float strafe_efficiency_average = player.strafe_efficiency_sum / player.strafe_efficiency_tics;
 +
 +      if(player.strafe_efficiency_best < strafe_efficiency_average)
 +      {
 +              player.strafe_efficiency_best = strafe_efficiency_average;
 +              PlayerScore_Set(player, SP_CTS_STRAFE, player.strafe_efficiency_best * 10000);
 +      }
  
        // useful to prevent cheating by running back to the start line and starting out with more speed
        if(autocvar_g_cts_finish_kill_delay)
@@@ -436,5 -365,6 +382,6 @@@ MUTATOR_HOOKFUNCTION(cts, ForbidDropCur
  
  void cts_Initialize()
  {
+       record_type = CTS_RECORD;
        cts_ScoreRules();
  }