]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/client/hud/panel/scoreboard.qc
Port more scoreboard cvars to the hud skins
[xonotic/xonotic-data.pk3dir.git] / qcsrc / client / hud / panel / scoreboard.qc
1 #include "scoreboard.qh"
2
3 #include "quickmenu.qh"
4 #include <common/ent_cs.qh>
5 #include <common/constants.qh>
6 #include <common/mapinfo.qh>
7 #include <common/minigames/cl_minigames.qh>
8 #include <common/stats.qh>
9 #include <common/teams.qh>
10
11 float sbt_bg_alpha;
12 float sbt_fg_alpha;
13 float sbt_fg_alpha_self;
14 bool sbt_highlight;
15 float sbt_highlight_alpha;
16 float sbt_highlight_alpha_self;
17
18 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
19 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
20 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
21 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0.7;
22 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
23 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
24 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
25 bool autocvar_hud_panel_scoreboard_table_highlight = true;
26 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
27 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.5;
28
29 bool autocvar_hud_panel_scoreboard_accuracy = true;
30 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
31 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
32
33 bool autocvar_hud_panel_scoreboard_dynamichud = false;
34
35
36 void drawstringright(vector, string, vector, vector, float, float);
37 void drawstringcenter(vector, string, vector, vector, float, float);
38
39 // wrapper to put all possible scores titles through gettext
40 string TranslateScoresLabel(string l)
41 {
42         switch(l)
43         {
44                 case "bckills": return CTX(_("SCO^bckills"));
45                 case "bctime": return CTX(_("SCO^bctime"));
46                 case "caps": return CTX(_("SCO^caps"));
47                 case "captime": return CTX(_("SCO^captime"));
48                 case "deaths": return CTX(_("SCO^deaths"));
49                 case "destroyed": return CTX(_("SCO^destroyed"));
50                 case "dmg": return CTX(_("SCO^dmg"));
51                 case "dmgtaken": return CTX(_("SCO^dmgtaken"));
52                 case "drops": return CTX(_("SCO^drops"));
53                 case "faults": return CTX(_("SCO^faults"));
54                 case "fckills": return CTX(_("SCO^fckills"));
55                 case "goals": return CTX(_("SCO^goals"));
56                 case "kckills": return CTX(_("SCO^kckills"));
57                 case "kdratio": return CTX(_("SCO^kdratio"));
58                 case "k/d": return CTX(_("SCO^k/d"));
59                 case "kd": return CTX(_("SCO^kd"));
60                 case "kdr": return CTX(_("SCO^kdr"));
61                 case "kills": return CTX(_("SCO^kills"));
62                 case "laps": return CTX(_("SCO^laps"));
63                 case "lives": return CTX(_("SCO^lives"));
64                 case "losses": return CTX(_("SCO^losses"));
65                 case "name": return CTX(_("SCO^name"));
66                 case "sum": return CTX(_("SCO^sum"));
67                 case "nick": return CTX(_("SCO^nick"));
68                 case "objectives": return CTX(_("SCO^objectives"));
69                 case "pickups": return CTX(_("SCO^pickups"));
70                 case "ping": return CTX(_("SCO^ping"));
71                 case "pl": return CTX(_("SCO^pl"));
72                 case "pushes": return CTX(_("SCO^pushes"));
73                 case "rank": return CTX(_("SCO^rank"));
74                 case "returns": return CTX(_("SCO^returns"));
75                 case "revivals": return CTX(_("SCO^revivals"));
76                 case "score": return CTX(_("SCO^score"));
77                 case "suicides": return CTX(_("SCO^suicides"));
78                 case "takes": return CTX(_("SCO^takes"));
79                 case "ticks": return CTX(_("SCO^ticks"));
80                 default: return l;
81         }
82 }
83
84 void HUD_InitScores()
85 {
86         int i, f;
87
88         ps_primary = ps_secondary = ts_primary = ts_secondary = -1;
89         for(i = 0; i < MAX_SCORE; ++i)
90         {
91                 f = (scores_flags[i] & SFL_SORT_PRIO_MASK);
92                 if(f == SFL_SORT_PRIO_PRIMARY)
93                         ps_primary = i;
94                 if(f == SFL_SORT_PRIO_SECONDARY)
95                         ps_secondary = i;
96         }
97         if(ps_secondary == -1)
98                 ps_secondary = ps_primary;
99
100         for(i = 0; i < MAX_TEAMSCORE; ++i)
101         {
102                 f = (teamscores_flags[i] & SFL_SORT_PRIO_MASK);
103                 if(f == SFL_SORT_PRIO_PRIMARY)
104                         ts_primary = i;
105                 if(f == SFL_SORT_PRIO_SECONDARY)
106                         ts_secondary = i;
107         }
108         if(ts_secondary == -1)
109                 ts_secondary = ts_primary;
110
111         Cmd_Scoreboard_SetFields(0);
112 }
113
114 float SetTeam(entity pl, float Team);
115 //float lastpnum;
116 void Scoreboard_UpdatePlayerTeams()
117 {
118         float Team;
119         entity pl, tmp;
120         float num;
121
122         num = 0;
123         for(pl = players.sort_next; pl; pl = pl.sort_next)
124         {
125                 num += 1;
126                 Team = entcs_GetScoreTeam(pl.sv_entnum);
127                 if(SetTeam(pl, Team))
128                 {
129                         tmp = pl.sort_prev;
130                         HUD_UpdatePlayerPos(pl);
131                         if(tmp)
132                                 pl = tmp;
133                         else
134                                 pl = players.sort_next;
135                 }
136         }
137         /*
138         if(num != lastpnum)
139                 print(strcat("PNUM: ", ftos(num), "\n"));
140         lastpnum = num;
141         */
142 }
143
144 int HUD_CompareScore(int vl, int vr, int f)
145 {
146     TC(int, vl); TC(int, vr); TC(int, f);
147         if(f & SFL_ZERO_IS_WORST)
148         {
149                 if(vl == 0 && vr != 0)
150                         return 1;
151                 if(vl != 0 && vr == 0)
152                         return 0;
153         }
154         if(vl > vr)
155                 return IS_INCREASING(f);
156         if(vl < vr)
157                 return IS_DECREASING(f);
158         return -1;
159 }
160
161 float HUD_ComparePlayerScores(entity left, entity right)
162 {
163         float vl, vr, r;
164         vl = entcs_GetTeam(left.sv_entnum);
165         vr = entcs_GetTeam(right.sv_entnum);
166
167         if(!left.gotscores)
168                 vl = NUM_SPECTATOR;
169         if(!right.gotscores)
170                 vr = NUM_SPECTATOR;
171
172         if(vl > vr)
173                 return true;
174         if(vl < vr)
175                 return false;
176
177         if(vl == NUM_SPECTATOR)
178         {
179                 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
180                 // no other sorting
181                 if(!left.gotscores && right.gotscores)
182                         return true;
183                 return false;
184         }
185
186         r = HUD_CompareScore(left.scores[ps_primary], right.scores[ps_primary], scores_flags[ps_primary]);
187         if (r >= 0)
188                 return r;
189
190         r = HUD_CompareScore(left.scores[ps_secondary], right.scores[ps_secondary], scores_flags[ps_secondary]);
191         if (r >= 0)
192                 return r;
193
194         int i;
195         for(i = 0; i < MAX_SCORE; ++i)
196         {
197                 r = HUD_CompareScore(left.scores[i], right.scores[i], scores_flags[i]);
198                 if (r >= 0)
199                         return r;
200         }
201
202         if (left.sv_entnum < right.sv_entnum)
203                 return true;
204
205         return false;
206 }
207
208 void HUD_UpdatePlayerPos(entity player)
209 {
210         entity ent;
211         for(ent = player.sort_next; ent && HUD_ComparePlayerScores(player, ent); ent = player.sort_next)
212         {
213                 SORT_SWAP(player, ent);
214         }
215         for(ent = player.sort_prev; ent != players && HUD_ComparePlayerScores(ent, player); ent = player.sort_prev)
216         {
217                 SORT_SWAP(ent, player);
218         }
219 }
220
221 float HUD_CompareTeamScores(entity left, entity right)
222 {
223         int i, r;
224
225         if(left.team == NUM_SPECTATOR)
226                 return 1;
227         if(right.team == NUM_SPECTATOR)
228                 return 0;
229
230         r = HUD_CompareScore(left.teamscores[ts_primary], right.teamscores[ts_primary], teamscores_flags[ts_primary]);
231         if (r >= 0)
232                 return r;
233
234         r = HUD_CompareScore(left.teamscores[ts_secondary], right.teamscores[ts_secondary], teamscores_flags[ts_secondary]);
235         if (r >= 0)
236                 return r;
237
238         for(i = 0; i < MAX_SCORE; ++i)
239         {
240                 r = HUD_CompareScore(left.teamscores[i], right.teamscores[i], teamscores_flags[i]);
241                 if (r >= 0)
242                         return r;
243         }
244
245         if (left.team < right.team)
246                 return true;
247
248         return false;
249 }
250
251 void HUD_UpdateTeamPos(entity Team)
252 {
253         entity ent;
254         for(ent = Team.sort_next; ent && HUD_CompareTeamScores(Team, ent); ent = Team.sort_next)
255         {
256                 SORT_SWAP(Team, ent);
257         }
258         for(ent = Team.sort_prev; ent != teams && HUD_CompareTeamScores(ent, Team); ent = Team.sort_prev)
259         {
260                 SORT_SWAP(ent, Team);
261         }
262 }
263
264 void Cmd_Scoreboard_Help()
265 {
266         LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command.\n"));
267         LOG_INFO(_("^3|---------------------------------------------------------------|\n"));
268         LOG_INFO(_("Usage:\n"));
269         LOG_INFO(_("^2scoreboard_columns_set default\n"));
270         LOG_INFO(_("^2scoreboard_columns_set ^7field1 field2 ...\n"));
271         LOG_INFO(_("The following field names are recognized (case insensitive):\n"));
272         LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields.\n\n"));
273
274         LOG_INFO(_("^3name^7 or ^3nick^7             Name of a player\n"));
275         LOG_INFO(_("^3ping^7                     Ping time\n"));
276         LOG_INFO(_("^3pl^7                       Packet loss\n"));
277         LOG_INFO(_("^3kills^7                    Number of kills\n"));
278         LOG_INFO(_("^3deaths^7                   Number of deaths\n"));
279         LOG_INFO(_("^3suicides^7                 Number of suicides\n"));
280         LOG_INFO(_("^3frags^7                    kills - suicides\n"));
281         LOG_INFO(_("^3kd^7                       The kill-death ratio\n"));
282         LOG_INFO(_("^3dmg^7                      The total damage done\n"));
283         LOG_INFO(_("^3dmgtaken^7                 The total damage taken\n"));
284         LOG_INFO(_("^3sum^7                      frags - deaths\n"));
285         LOG_INFO(_("^3caps^7                     How often a flag (CTF) or a key (KeyHunt) was captured\n"));
286         LOG_INFO(_("^3pickups^7                  How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up\n"));
287         LOG_INFO(_("^3captime^7                  Time of fastest cap (CTF)\n"));
288         LOG_INFO(_("^3fckills^7                  Number of flag carrier kills\n"));
289         LOG_INFO(_("^3returns^7                  Number of flag returns\n"));
290         LOG_INFO(_("^3drops^7                    Number of flag drops\n"));
291         LOG_INFO(_("^3lives^7                    Number of lives (LMS)\n"));
292         LOG_INFO(_("^3rank^7                     Player rank\n"));
293         LOG_INFO(_("^3pushes^7                   Number of players pushed into void\n"));
294         LOG_INFO(_("^3destroyed^7                Number of keys destroyed by pushing them into void\n"));
295         LOG_INFO(_("^3kckills^7                  Number of keys carrier kills\n"));
296         LOG_INFO(_("^3losses^7                   Number of times a key was lost\n"));
297         LOG_INFO(_("^3laps^7                     Number of laps finished (race/cts)\n"));
298         LOG_INFO(_("^3time^7                     Total time raced (race/cts)\n"));
299         LOG_INFO(_("^3fastest^7                  Time of fastest lap (race/cts)\n"));
300         LOG_INFO(_("^3ticks^7                    Number of ticks (DOM)\n"));
301         LOG_INFO(_("^3takes^7                    Number of domination points taken (DOM)\n"));
302         LOG_INFO(_("^3bckills^7                  Number of ball carrier kills\n"));
303         LOG_INFO(_("^3bctime^7                   Total amount of time holding the ball in Keepaway\n"));
304         LOG_INFO(_("^3score^7                    Total score\n\n"));
305
306         LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
307                 "of game types, then a slash, to make the field show up only in these\n"
308                 "or in all but these game types. You can also specify 'all' as a\n"
309                 "field to show all fields available for the current game mode.\n\n"));
310
311         LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
312                 "include/exclude ALL teams/noteams game modes.\n\n"));
313
314         LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4\n"));
315         LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
316                 "right of the vertical bar aligned to the right.\n"));
317         LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
318                 "other gamemodes except DM.\n"));
319 }
320
321 // NOTE: adding a gametype with ? to not warn for an optional field
322 // make sure it's excluded in a previous exclusive rule, if any
323 // otherwise the previous exclusive rule warns anyway
324 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
325 #define SCOREBOARD_DEFAULT_COLUMNS \
326 "ping pl name |" \
327 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
328 " -teams,lms/deaths +ft,tdm/deaths" \
329 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
330 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
331 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
332 " +ctf/caps +ctf/pickups +ctf/fckills +ctf/returns +ons/caps +ons/takes" \
333 " +lms/lives +lms/rank" \
334 " +kh/caps +kh/pushes +kh/destroyed" \
335 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
336 " +as/objectives +nb/faults +nb/goals" \
337 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
338 " -lms,rc,cts,inv,nb/score"
339
340 void Cmd_Scoreboard_SetFields(int argc)
341 {
342     TC(int, argc);
343         int i, j, slash;
344         string str, pattern;
345         float have_name = 0, have_primary = 0, have_secondary = 0, have_separator = 0;
346         float missing;
347
348         if(!gametype)
349         {
350                 // set up a temporary scoreboard layout
351                 // no layout can be properly set up until score_info data haven't been received
352                 argc = tokenizebyseparator("0 1 ping pl name | score", " ");
353                 ps_primary = 0;
354                 scores_label[ps_primary] = strzone("score");
355                 scores_flags[ps_primary] = SFL_ALLOW_HIDE;
356         }
357
358         // TODO: re enable with gametype dependant cvars?
359         if(argc < 3) // no arguments provided
360                 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
361
362         if(argc < 3)
363                 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
364
365         if(argc == 3)
366         {
367                 if(argv(2) == "default")
368                         argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
369                 else if(argv(2) == "all")
370                 {
371                         string s;
372                         s = "ping pl name |";
373                         for(i = 0; i < MAX_SCORE; ++i)
374                         {
375                                 if(i != ps_primary)
376                                 if(i != ps_secondary)
377                                 if(scores_label[i] != "")
378                                         s = strcat(s, " ", scores_label[i]);
379                         }
380                         if(ps_secondary != ps_primary)
381                                 s = strcat(s, " ", scores_label[ps_secondary]);
382                         s = strcat(s, " ", scores_label[ps_primary]);
383                         argc = tokenizebyseparator(strcat("0 1 ", s), " ");
384                 }
385         }
386
387
388         sbt_num_fields = 0;
389
390         hud_fontsize = HUD_GetFontsize("hud_fontsize");
391
392         for(i = 1; i < argc - 1; ++i)
393         {
394                 float nocomplain;
395                 str = argv(i+1);
396
397                 nocomplain = false;
398                 if(substring(str, 0, 1) == "?")
399                 {
400                         nocomplain = true;
401                         str = substring(str, 1, strlen(str) - 1);
402                 }
403
404                 slash = strstrofs(str, "/", 0);
405                 if(slash >= 0)
406                 {
407                         pattern = substring(str, 0, slash);
408                         str = substring(str, slash + 1, strlen(str) - (slash + 1));
409
410                         if (!isGametypeInFilter(gametype, teamplay, false, pattern))
411                                 continue;
412                 }
413
414                 strunzone(sbt_field_title[sbt_num_fields]);
415                 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(str));
416                 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
417                 str = strtolower(str);
418
419                 switch(str)
420                 {
421                         case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
422                         case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
423                         case "kd": case "kdr": case "kdratio": case "k/d": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
424                         case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
425                         case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
426                         case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
427                         case "dmg": sbt_field[sbt_num_fields] = SP_DMG; break;
428                         case "dmgtaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
429                         default:
430                         {
431                                 for(j = 0; j < MAX_SCORE; ++j)
432                                         if(str == strtolower(scores_label[j]))
433                                                 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
434
435 LABEL(notfound)
436                                 if(str == "frags")
437                                         j = SP_FRAGS;
438                                 else
439                                 {
440                                         if(!nocomplain)
441                                                 LOG_INFOF("^1Error:^7 Unknown score field: '%s'\n", str);
442                                         continue;
443                                 }
444 LABEL(found)
445                                 sbt_field[sbt_num_fields] = j;
446                                 if(j == ps_primary)
447                                         have_primary = 1;
448                                 if(j == ps_secondary)
449                                         have_secondary = 1;
450
451                         }
452                 }
453                 ++sbt_num_fields;
454                 if(sbt_num_fields >= MAX_SBT_FIELDS)
455                         break;
456         }
457
458         if(scores_flags[ps_primary] & SFL_ALLOW_HIDE)
459                 have_primary = 1;
460         if(scores_flags[ps_secondary] & SFL_ALLOW_HIDE)
461                 have_secondary = 1;
462         if(ps_primary == ps_secondary)
463                 have_secondary = 1;
464         missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
465
466         if(sbt_num_fields + missing < MAX_SBT_FIELDS)
467         {
468                 if(!have_name)
469                 {
470                         strunzone(sbt_field_title[sbt_num_fields]);
471                         for(i = sbt_num_fields; i > 0; --i)
472                         {
473                                 sbt_field_title[i] = sbt_field_title[i-1];
474                                 sbt_field_size[i] = sbt_field_size[i-1];
475                                 sbt_field[i] = sbt_field[i-1];
476                         }
477                         sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
478                         sbt_field[0] = SP_NAME;
479                         ++sbt_num_fields;
480                         LOG_INFO("fixed missing field 'name'\n");
481
482                         if(!have_separator)
483                         {
484                                 strunzone(sbt_field_title[sbt_num_fields]);
485                                 for(i = sbt_num_fields; i > 1; --i)
486                                 {
487                                         sbt_field_title[i] = sbt_field_title[i-1];
488                                         sbt_field_size[i] = sbt_field_size[i-1];
489                                         sbt_field[i] = sbt_field[i-1];
490                                 }
491                                 sbt_field_title[1] = strzone("|");
492                                 sbt_field[1] = SP_SEPARATOR;
493                                 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
494                                 ++sbt_num_fields;
495                                 LOG_INFO("fixed missing field '|'\n");
496                         }
497                 }
498                 else if(!have_separator)
499                 {
500                         strunzone(sbt_field_title[sbt_num_fields]);
501                         sbt_field_title[sbt_num_fields] = strzone("|");
502                         sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
503                         sbt_field[sbt_num_fields] = SP_SEPARATOR;
504                         ++sbt_num_fields;
505                         LOG_INFO("fixed missing field '|'\n");
506                 }
507                 if(!have_secondary)
508                 {
509                         strunzone(sbt_field_title[sbt_num_fields]);
510                         sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label[ps_secondary]));
511                         sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
512                         sbt_field[sbt_num_fields] = ps_secondary;
513                         ++sbt_num_fields;
514                         LOG_INFOF("fixed missing field '%s'\n", scores_label[ps_secondary]);
515                 }
516                 if(!have_primary)
517                 {
518                         strunzone(sbt_field_title[sbt_num_fields]);
519                         sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label[ps_primary]));
520                         sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
521                         sbt_field[sbt_num_fields] = ps_primary;
522                         ++sbt_num_fields;
523                         LOG_INFOF("fixed missing field '%s'\n", scores_label[ps_primary]);
524                 }
525         }
526
527         sbt_field[sbt_num_fields] = SP_END;
528 }
529
530 // MOVEUP::
531 vector sbt_field_rgb;
532 string sbt_field_icon0;
533 string sbt_field_icon1;
534 string sbt_field_icon2;
535 vector sbt_field_icon0_rgb;
536 vector sbt_field_icon1_rgb;
537 vector sbt_field_icon2_rgb;
538 float sbt_field_icon0_alpha;
539 float sbt_field_icon1_alpha;
540 float sbt_field_icon2_alpha;
541 string HUD_GetField(entity pl, int field)
542 {
543     TC(int, field);
544         float tmp, num, denom;
545         int f;
546         string str;
547         sbt_field_rgb = '1 1 1';
548         sbt_field_icon0 = "";
549         sbt_field_icon1 = "";
550         sbt_field_icon2 = "";
551         sbt_field_icon0_rgb = '1 1 1';
552         sbt_field_icon1_rgb = '1 1 1';
553         sbt_field_icon2_rgb = '1 1 1';
554         sbt_field_icon0_alpha = 1;
555         sbt_field_icon1_alpha = 1;
556         sbt_field_icon2_alpha = 1;
557         switch(field)
558         {
559                 case SP_PING:
560                         if (!pl.gotscores)
561                                 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
562                         //str = getplayerkeyvalue(pl.sv_entnum, "ping");
563                         f = pl.ping;
564                         if(f == 0)
565                                 return _("N/A");
566                         tmp = max(0, min(220, f-80)) / 220;
567                         sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
568                         return ftos(f);
569
570                 case SP_PL:
571                         if (!pl.gotscores)
572                                 return _("N/A");
573                         f = pl.ping_packetloss;
574                         tmp = pl.ping_movementloss;
575                         if(f == 0 && tmp == 0)
576                                 return "";
577                         str = ftos(ceil(f * 100));
578                         if(tmp != 0)
579                                 str = strcat(str, "~", ftos(ceil(tmp * 100)));
580                         tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
581                         sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
582                         return str;
583
584                 case SP_NAME:
585                         if(ready_waiting && pl.ready)
586                         {
587                                 sbt_field_icon0 = "gfx/scoreboard/player_ready";
588                         }
589                         else if(!teamplay)
590                         {
591                                 f = stof(getplayerkeyvalue(pl.sv_entnum, "colors"));
592                                 {
593                                         sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
594                                         sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
595                                         sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
596                                         sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
597                                         sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
598                                 }
599                         }
600                         return entcs_GetName(pl.sv_entnum);
601
602                 case SP_FRAGS:
603                         f = pl.(scores[SP_KILLS]);
604                         f -= pl.(scores[SP_SUICIDES]);
605                         return ftos(f);
606
607                 case SP_KDRATIO:
608                         num = pl.(scores[SP_KILLS]);
609                         denom = pl.(scores[SP_DEATHS]);
610
611                         if(denom == 0) {
612                                 sbt_field_rgb = '0 1 0';
613                                 str = sprintf("%d", num);
614                         } else if(num <= 0) {
615                                 sbt_field_rgb = '1 0 0';
616                                 str = sprintf("%.1f", num/denom);
617                         } else
618                                 str = sprintf("%.1f", num/denom);
619                         return str;
620
621                 case SP_SUM:
622                         f = pl.(scores[SP_KILLS]);
623                         f -= pl.(scores[SP_DEATHS]);
624
625                         if(f > 0) {
626                                 sbt_field_rgb = '0 1 0';
627                         } else if(f == 0) {
628                                 sbt_field_rgb = '1 1 1';
629                         } else {
630                                 sbt_field_rgb = '1 0 0';
631                         }
632                         return ftos(f);
633
634                 case SP_DMG:
635                         num = pl.(scores[SP_DMG]);
636                         denom = 1000;
637
638                         str = sprintf("%.1f k", num/denom);
639                         return str;
640
641                 case SP_DMGTAKEN:
642                         num = pl.(scores[SP_DMGTAKEN]);
643                         denom = 1000;
644
645                         str = sprintf("%.1f k", num/denom);
646                         return str;
647
648                 default:
649                         tmp = pl.(scores[field]);
650                         f = scores_flags[field];
651                         if(field == ps_primary)
652                                 sbt_field_rgb = '1 1 0';
653                         else if(field == ps_secondary)
654                                 sbt_field_rgb = '0 1 1';
655                         else
656                                 sbt_field_rgb = '1 1 1';
657                         return ScoreString(f, tmp);
658         }
659         //return "error";
660 }
661
662 float sbt_fixcolumnwidth_len;
663 float sbt_fixcolumnwidth_iconlen;
664 float sbt_fixcolumnwidth_marginlen;
665
666 string HUD_FixScoreboardColumnWidth(int i, string str)
667 {
668     TC(int, i);
669         float field, f;
670         vector sz;
671         field = sbt_field[i];
672
673         sbt_fixcolumnwidth_iconlen = 0;
674
675         if(sbt_field_icon0 != "")
676         {
677                 sz = draw_getimagesize(sbt_field_icon0);
678                 f = sz.x / sz.y;
679                 if(sbt_fixcolumnwidth_iconlen < f)
680                         sbt_fixcolumnwidth_iconlen = f;
681         }
682
683         if(sbt_field_icon1 != "")
684         {
685                 sz = draw_getimagesize(sbt_field_icon1);
686                 f = sz.x / sz.y;
687                 if(sbt_fixcolumnwidth_iconlen < f)
688                         sbt_fixcolumnwidth_iconlen = f;
689         }
690
691         if(sbt_field_icon2 != "")
692         {
693                 sz = draw_getimagesize(sbt_field_icon2);
694                 f = sz.x / sz.y;
695                 if(sbt_fixcolumnwidth_iconlen < f)
696                         sbt_fixcolumnwidth_iconlen = f;
697         }
698
699         sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
700
701         if(sbt_fixcolumnwidth_iconlen != 0)
702                 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
703         else
704                 sbt_fixcolumnwidth_marginlen = 0;
705
706         if(field == SP_NAME) // name gets all remaining space
707         {
708                 int j;
709                 float namesize;
710                 namesize = panel_size.x;
711                 for(j = 0; j < sbt_num_fields; ++j)
712                         if(j != i)
713                                 if (sbt_field[i] != SP_SEPARATOR)
714                                         namesize -= sbt_field_size[j] + hud_fontsize.x;
715                 namesize += hud_fontsize.x;
716                 sbt_field_size[i] = namesize;
717
718                 if (sbt_fixcolumnwidth_iconlen != 0)
719                         namesize -= sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen;
720                 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
721                 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
722         }
723         else
724                 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
725
726         f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen;
727         if(sbt_field_size[i] < f)
728                 sbt_field_size[i] = f;
729
730         return str;
731 }
732
733 vector HUD_PrintScoreboardHeader(vector pos, vector rgb)
734 {
735         int i;
736         vector column_dim = eY * panel_size.y;
737         vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
738         for(i = 0; i < sbt_num_fields; ++i)
739         {
740                 if(sbt_field[i] == SP_SEPARATOR)
741                         break;
742                 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
743                 if (sbt_highlight)
744                         if (i % 2)
745                                 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
746                 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
747                 pos.x += column_dim.x;
748         }
749         if(sbt_field[i] == SP_SEPARATOR)
750         {
751                 pos.x = panel_pos.x + panel_size.x;
752                 for(i = sbt_num_fields - 1; i > 0; --i)
753                 {
754                         if(sbt_field[i] == SP_SEPARATOR)
755                                 break;
756
757                         pos.x -= sbt_field_size[i];
758
759                         if (sbt_highlight)
760                                 if (!(i % 2))
761                                 {
762                                         if (i == sbt_num_fields-1)
763                                                 column_dim.x = sbt_field_size[i] + hud_fontsize.x * 0.5;
764                                         else
765                                                 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
766                                         drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
767                                 }
768
769                         text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
770                         drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
771                         pos.x -= hud_fontsize.x;
772                 }
773         }
774
775         pos.x = panel_pos.x;
776         pos.y += 1.25 * hud_fontsize.y;
777         return pos;
778 }
779
780 void HUD_PrintScoreboardItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
781 {
782     TC(bool, is_self); TC(int, pl_number);
783         string str;
784         int field;
785         float is_spec;
786         is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
787         if(is_spec && !is_self)
788                 rgb = '0 0 0';
789
790         vector h_pos = item_pos;
791         vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
792         // alternated rows highlighting
793         if(is_self)
794                 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
795         else if((sbt_highlight) && (!(pl_number % 2)))
796                 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
797
798         vector pos = item_pos;
799         pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
800         vector tmp = '0 0 0';
801         int i;
802         for(i = 0; i < sbt_num_fields; ++i)
803         {
804                 field = sbt_field[i];
805                 if(field == SP_SEPARATOR)
806                         break;
807
808                 if(is_spec && field != SP_NAME && field != SP_PING) {
809                         pos.x += sbt_field_size[i] + hud_fontsize.x;
810                         continue;
811                 }
812                 str = HUD_GetField(pl, field);
813                 str = HUD_FixScoreboardColumnWidth(i, str);
814
815                 pos.x += sbt_field_size[i] + hud_fontsize.x;
816
817                 if(field == SP_NAME) {
818                         tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
819                         if (is_self)
820                                 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
821                         else
822                                 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
823                 } else {
824                         tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
825                         if (is_self)
826                                 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
827                         else
828                                 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
829                 }
830
831                 tmp.x = sbt_field_size[i] + hud_fontsize.x;
832                 if(sbt_field_icon0 != "")
833                         if (is_self)
834                                 drawpic(pos - tmp, sbt_field_icon0, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon0_alpha * sbt_fg_alpha_self, DRAWFLAG_NORMAL);
835                         else
836                                 drawpic(pos - tmp, sbt_field_icon0, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon0_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
837                 if(sbt_field_icon1 != "")
838                         if (is_self)
839                                 drawpic(pos - tmp, sbt_field_icon1, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon1_alpha * sbt_fg_alpha_self, DRAWFLAG_NORMAL);
840                         else
841                                 drawpic(pos - tmp, sbt_field_icon1, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon1_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
842                 if(sbt_field_icon2 != "")
843                         if (is_self)
844                                 drawpic(pos - tmp, sbt_field_icon2, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon2_rgb, sbt_field_icon2_alpha * sbt_fg_alpha_self, DRAWFLAG_NORMAL);
845                         else
846                                 drawpic(pos - tmp, sbt_field_icon2, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon2_rgb, sbt_field_icon2_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
847         }
848
849         if(sbt_field[i] == SP_SEPARATOR)
850         {
851                 pos.x = item_pos.x + panel_size.x;
852                 for(i = sbt_num_fields-1; i > 0; --i)
853                 {
854                         field = sbt_field[i];
855                         if(field == SP_SEPARATOR)
856                                 break;
857
858                         if(is_spec && field != SP_NAME && field != SP_PING) {
859                                 pos.x -= sbt_field_size[i] + hud_fontsize.x;
860                                 continue;
861                         }
862
863                         str = HUD_GetField(pl, field);
864                         str = HUD_FixScoreboardColumnWidth(i, str);
865
866                         if(field == SP_NAME) {
867                                 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
868                                 if(is_self)
869                                         drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
870                                 else
871                                         drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
872                         } else {
873                                 tmp.x = sbt_fixcolumnwidth_len;
874                                 if(is_self)
875                                         drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
876                                 else
877                                         drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
878                         }
879
880                         tmp.x = sbt_field_size[i];
881                         if(sbt_field_icon0 != "")
882                                 if (is_self)
883                                         drawpic(pos - tmp, sbt_field_icon0, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon0_alpha * sbt_fg_alpha_self, DRAWFLAG_NORMAL);
884                                 else
885                                         drawpic(pos - tmp, sbt_field_icon0, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon0_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
886                         if(sbt_field_icon1 != "")
887                                 if (is_self)
888                                         drawpic(pos - tmp, sbt_field_icon1, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon1_alpha * sbt_fg_alpha_self, DRAWFLAG_NORMAL);
889                                 else
890                                         drawpic(pos - tmp, sbt_field_icon1, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon1_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
891                         if(sbt_field_icon2 != "")
892                                 if (is_self)
893                                         drawpic(pos - tmp, sbt_field_icon2, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon2_rgb, sbt_field_icon2_alpha * sbt_fg_alpha_self, DRAWFLAG_NORMAL);
894                                 else
895                                         drawpic(pos - tmp, sbt_field_icon2, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon2_rgb, sbt_field_icon2_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
896                         pos.x -= sbt_field_size[i] + hud_fontsize.x;
897                 }
898         }
899
900         if(pl.eliminated)
901                 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
902 }
903
904 vector HUD_Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
905 {
906         entity pl;
907
908         panel_pos = pos;
909         panel_size.y = 1.25 * hud_fontsize.y * (1 + max(1, tm.team_size));
910         panel_size.y += panel_bg_padding * 2;
911         HUD_Panel_DrawBg(scoreboard_fade_alpha);
912
913         vector end_pos = panel_pos + eY * (panel_size.y + panel_bg_border * 2 + hud_fontsize.y);
914
915         if(panel_bg_padding)
916         {
917                 panel_pos += '1 1 0' * panel_bg_padding;
918                 panel_size -= '2 2 0' * panel_bg_padding;
919         }
920
921         pos = panel_pos;
922         vector tmp = eX * panel_size.x + eY * 1.25 * hud_fontsize.y;
923
924         // rounded header
925         if (sbt_bg_alpha)
926                 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
927
928         pos.y += 1.25 * hud_fontsize.y;
929
930         // table background
931         tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
932         if (sbt_bg_alpha)
933                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
934
935
936         // print header row and highlight columns
937         pos = HUD_PrintScoreboardHeader(panel_pos, rgb);
938
939         // fill the table and draw the rows
940         int i = 0;
941         if (teamplay)
942                 for(pl = players.sort_next; pl; pl = pl.sort_next)
943                 {
944                         if(pl.team != tm.team)
945                                 continue;
946                         HUD_PrintScoreboardItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
947                         pos.y += 1.25 * hud_fontsize.y;
948                         ++i;
949                 }
950         else
951                 for(pl = players.sort_next; pl; pl = pl.sort_next)
952                 {
953                         if(pl.team == NUM_SPECTATOR)
954                                 continue;
955                         HUD_PrintScoreboardItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
956                         pos.y += 1.25 * hud_fontsize.y;
957                         ++i;
958                 }
959
960         panel_size.x += panel_bg_padding * 2; // restore initial width
961         return end_pos;
962 }
963
964 float HUD_WouldDrawScoreboard() {
965         if (QuickMenu_IsOpened())
966                 return 0;
967         else if (HUD_Radar_Clickable())
968                 return 0;
969         else if (scoreboard_showscores)
970                 return 1;
971         else if (intermission == 1)
972                 return 1;
973         else if (intermission == 2)
974                 return 0;
975         else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
976                 return 1;
977         else if (scoreboard_showscores_force)
978                 return 1;
979         return 0;
980 }
981
982 float average_accuracy;
983 vector HUD_DrawScoreboardAccuracyStats(vector pos, vector rgb, vector bg_size)
984 {
985         WepSet weapons_stat = WepSet_GetFromStat();
986         WepSet weapons_inmap = WepSet_GetFromStat_InMap();
987         int disownedcnt = 0;
988         FOREACH(Weapons, it != WEP_Null, {
989                 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
990
991                 WepSet set = it.m_wepset;
992                 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
993                         ++disownedcnt;
994         });
995
996         int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt;
997         if (weapon_cnt <= 0) return pos;
998
999         int rows = 1;
1000         if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - 1) * 0.5))
1001                 rows = 2;
1002         int columnns = ceil(weapon_cnt / rows);
1003
1004         float height = 40;
1005
1006         drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1007         pos.y += 1.25 * hud_fontsize.y;
1008         pos.y += panel_bg_border;
1009
1010         panel_pos = pos;
1011         panel_size.y = height * rows;
1012         panel_size.y += panel_bg_padding * 2;
1013         HUD_Panel_DrawBg(scoreboard_fade_alpha);
1014
1015         vector end_pos = panel_pos + eY * (panel_size.y + panel_bg_border * 2 + hud_fontsize.y);
1016
1017         if(panel_bg_padding)
1018         {
1019                 panel_pos += '1 1 0' * panel_bg_padding;
1020                 panel_size -= '2 2 0' * panel_bg_padding;
1021         }
1022
1023         pos = panel_pos;
1024         vector tmp = panel_size;
1025
1026         float fontsize = height * 1/3;
1027         float weapon_height = height * 2/3;
1028         float weapon_width = tmp.x / columnns / rows;
1029
1030         if (sbt_bg_alpha)
1031                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1032
1033         if(sbt_highlight)
1034         {
1035                 // column highlighting
1036                 for (int i = 0; i < columnns; ++i)
1037                         if ((i % 2) == 0)
1038                                 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1039
1040                 // row highlighting
1041                 for (int i = 0; i < rows; ++i)
1042                         drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * fontsize, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1043         }
1044
1045         average_accuracy = 0;
1046         int weapons_with_stats = 0;
1047         if (rows == 2)
1048                 pos.x += weapon_width / 2;
1049
1050         if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1051                 rgb = '1 1 1';
1052         else
1053                 Accuracy_LoadColors();
1054
1055         float oldposx = pos.x;
1056         vector tmpos = pos;
1057
1058         int column = 0;
1059         FOREACH(Weapons, it != WEP_Null, {
1060                 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1061
1062                 WepSet set = it.m_wepset;
1063                 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1064                         continue;
1065
1066                 float weapon_alpha;
1067                 if (weapon_stats >= 0)
1068                         weapon_alpha = sbt_fg_alpha;
1069                 else
1070                         weapon_alpha = 0.2 * sbt_fg_alpha;
1071
1072                 // weapon icon
1073                 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1074                 // the accuracy
1075                 if (weapon_stats >= 0) {
1076                         weapons_with_stats += 1;
1077                         average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1078
1079                         string s;
1080                         s = sprintf("%d%%", weapon_stats * 100);
1081
1082                         float padding;
1083                         padding = (weapon_width - stringwidth(s, false, eX * fontsize)) / 2; // center the accuracy value
1084
1085                         if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1086                                 rgb = Accuracy_GetColor(weapon_stats);
1087
1088                         drawstring(tmpos + eX * padding + eY * weapon_height, s, '1 1 0' * fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1089                 }
1090                 tmpos.x += weapon_width * rows;
1091                 pos.x += weapon_width * rows;
1092                 if (rows == 2 && column == columnns - 1) {
1093                         tmpos.x = oldposx;
1094                         tmpos.y += height;
1095                         pos.y += height;
1096                 }
1097                 ++column;
1098         });
1099
1100         if (weapons_with_stats)
1101                 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1102
1103         panel_size.x += panel_bg_padding * 2; // restore initial width
1104         return end_pos;
1105 }
1106
1107 vector HUD_DrawKeyValue(vector pos, string key, string value) {
1108         float px = pos.x;
1109         pos.x += hud_fontsize.x * 0.25;
1110         drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1111         pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1112         drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1113         pos.x = px;
1114         pos.y += hud_fontsize.y;
1115
1116         return pos;
1117 }
1118
1119 vector HUD_DrawMapStats(vector pos, vector rgb, vector bg_size) {
1120         float stat_secrets_found, stat_secrets_total;
1121         float stat_monsters_killed, stat_monsters_total;
1122         float rows = 0;
1123         string val;
1124
1125         // get monster stats
1126         stat_monsters_killed = STAT(MONSTERS_KILLED);
1127         stat_monsters_total = STAT(MONSTERS_TOTAL);
1128
1129         // get secrets stats
1130         stat_secrets_found = STAT(SECRETS_FOUND);
1131         stat_secrets_total = STAT(SECRETS_TOTAL);
1132
1133         // get number of rows
1134         if(stat_secrets_total)
1135                 rows += 1;
1136         if(stat_monsters_total)
1137                 rows += 1;
1138
1139         // if no rows, return
1140         if (!rows)
1141                 return pos;
1142
1143         //  draw table header
1144         drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1145         pos.y += 1.25 * hud_fontsize.y;
1146         pos.y += panel_bg_border;
1147
1148         panel_pos = pos;
1149         panel_size.y = hud_fontsize.y * rows;
1150         panel_size.y += panel_bg_padding * 2;
1151         HUD_Panel_DrawBg(scoreboard_fade_alpha);
1152
1153         vector end_pos = panel_pos + eY * (panel_size.y + panel_bg_border * 2 + hud_fontsize.y);
1154
1155         if(panel_bg_padding)
1156         {
1157                 panel_pos += '1 1 0' * panel_bg_padding;
1158                 panel_size -= '2 2 0' * panel_bg_padding;
1159         }
1160
1161         pos = panel_pos;
1162         vector tmp = panel_size;
1163
1164         if (sbt_bg_alpha)
1165                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1166
1167         // draw monsters
1168         if(stat_monsters_total)
1169         {
1170                 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1171                 pos = HUD_DrawKeyValue(pos, _("Monsters killed:"), val);
1172         }
1173
1174         // draw secrets
1175         if(stat_secrets_total)
1176         {
1177                 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1178                 pos = HUD_DrawKeyValue(pos, _("Secrets found:"), val);
1179         }
1180
1181         panel_size.x += panel_bg_padding * 2; // restore initial width
1182         return end_pos;
1183 }
1184
1185
1186 vector HUD_DrawScoreboardRankings(vector pos, entity pl, vector rgb, vector bg_size)
1187 {
1188         int i;
1189         RANKINGS_RECEIVED_CNT = 0;
1190         for (i=RANKINGS_CNT-1; i>=0; --i)
1191                 if (grecordtime[i])
1192                         ++RANKINGS_RECEIVED_CNT;
1193
1194         if (RANKINGS_RECEIVED_CNT == 0)
1195                 return pos;
1196
1197         vector hl_rgb = rgb + '0.5 0.5 0.5';
1198
1199         pos.y += hud_fontsize.y;
1200         drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1201         pos.y += 1.25 * hud_fontsize.y;
1202         pos.y += panel_bg_border;
1203
1204         panel_pos = pos;
1205         panel_size.y = 1.25 * hud_fontsize.y * RANKINGS_RECEIVED_CNT;
1206         panel_size.y += panel_bg_padding * 2;
1207         HUD_Panel_DrawBg(scoreboard_fade_alpha);
1208
1209         vector end_pos = panel_pos + eY * (panel_size.y + panel_bg_border * 2 + hud_fontsize.y);
1210
1211         if(panel_bg_padding)
1212         {
1213                 panel_pos += '1 1 0' * panel_bg_padding;
1214                 panel_size -= '2 2 0' * panel_bg_padding;
1215         }
1216
1217         pos = panel_pos;
1218         vector tmp = panel_size;
1219
1220         if (sbt_bg_alpha)
1221                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1222
1223         // row highlighting
1224         for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1225         {
1226                 string n, p;
1227                 float t;
1228                 t = grecordtime[i];
1229                 if (t == 0)
1230                         continue;
1231                 n = grecordholder[i];
1232                 p = count_ordinal(i+1);
1233                 if(grecordholder[i] == entcs_GetName(player_localnum))
1234                         drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1235                 else if(!(i % 2) && sbt_highlight)
1236                         drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1237                 drawstring(pos, p, '1 1 0' * hud_fontsize.y, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1238                 drawstring(pos + '3 0 0' * hud_fontsize.y, TIME_ENCODED_TOSTRING(t), '1 1 0' * hud_fontsize.y, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1239                 drawcolorcodedstring(pos + '8 0 0' * hud_fontsize.y, n, '1 1 0' * hud_fontsize.y, sbt_fg_alpha, DRAWFLAG_NORMAL);
1240                 pos.y += 1.25 * hud_fontsize.y;
1241         }
1242
1243         panel_size.x += panel_bg_padding * 2; // restore initial width
1244         return end_pos;
1245 }
1246
1247 float hud_woulddrawscoreboard_prev;
1248 float hud_woulddrawscoreboard_change; // "time" at which HUD_WouldDrawScoreboard() changed
1249 void HUD_DrawScoreboard()
1250 {
1251         if(!autocvar__hud_configure)
1252         {
1253                 float hud_woulddrawscoreboard;
1254                 hud_woulddrawscoreboard = scoreboard_active;
1255                 if(hud_woulddrawscoreboard != hud_woulddrawscoreboard_prev) {
1256                         hud_woulddrawscoreboard_change = time;
1257                         hud_woulddrawscoreboard_prev = hud_woulddrawscoreboard;
1258                 }
1259
1260                 if(hud_woulddrawscoreboard) {
1261                         if(menu_enabled == 1)
1262                                 scoreboard_fade_alpha = 1;
1263                         float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1264                         if (scoreboard_fadeinspeed)
1265                                 scoreboard_fade_alpha = bound (0, (time - hud_woulddrawscoreboard_change) * scoreboard_fadeinspeed, 1);
1266                         else
1267                                 scoreboard_fade_alpha = 1;
1268                 }
1269                 else {
1270                         float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1271                         if (scoreboard_fadeoutspeed)
1272                                 scoreboard_fade_alpha = bound (0, (1/scoreboard_fadeoutspeed - (time - hud_woulddrawscoreboard_change)) * scoreboard_fadeoutspeed, 1);
1273                         else
1274                                 scoreboard_fade_alpha = 0;
1275                 }
1276
1277                 if (!scoreboard_fade_alpha)
1278                         return;
1279         }
1280         else
1281                 scoreboard_fade_alpha = 0;
1282
1283         if (autocvar_hud_panel_scoreboard_dynamichud)
1284                 HUD_Scale_Enable();
1285         else
1286                 HUD_Scale_Disable();
1287
1288         float hud_fade_alpha_save = hud_fade_alpha;
1289         if(menu_enabled == 1)
1290                 hud_fade_alpha = 1;
1291         else
1292                 hud_fade_alpha = scoreboard_fade_alpha * (1 - autocvar__menu_alpha);
1293         HUD_Panel_UpdateCvars();
1294
1295         sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1296         sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1297         sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1298         sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1299         sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1300         sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1301
1302         hud_fade_alpha = hud_fade_alpha_save;
1303
1304         // don't overlap with con_notify
1305         if(!autocvar__hud_configure)
1306                 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1307
1308         Scoreboard_UpdatePlayerTeams();
1309
1310         vector pos, tmp;
1311         entity pl, tm;
1312         string str;
1313
1314         // Initializes position
1315         pos = panel_pos;
1316
1317         // Heading
1318         vector sb_heading_fontsize;
1319         sb_heading_fontsize = hud_fontsize * 2;
1320         draw_beginBoldFont();
1321         drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1322         draw_endBoldFont();
1323
1324         pos.y += sb_heading_fontsize.y;
1325         pos.y += panel_bg_border;
1326
1327         // Draw the scoreboard
1328         float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1329         if(scale <= 0)
1330                 scale = 0.25;
1331         vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1332
1333         if(teamplay)
1334         {
1335                 vector team_score_baseoffset = eY * hud_fontsize.y - eX * (panel_bg_border + hud_fontsize.x * 0.5);
1336                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1337                 {
1338                         if(tm.team == NUM_SPECTATOR)
1339                                 continue;
1340                         if(!tm.team && teamplay)
1341                                 continue;
1342
1343                         draw_beginBoldFont();
1344                         vector rgb = Team_ColorRGB(tm.team);
1345                         str = ftos(tm.(teamscores[ts_primary]));
1346                         drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1347
1348                         if(ts_primary != ts_secondary)
1349                         {
1350                                 str = ftos(tm.(teamscores[ts_secondary]));
1351                                 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize) + eY * hud_fontsize.y * 1.5, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1352                         }
1353                         draw_endBoldFont();
1354
1355                         panel_bg_color = rgb * panel_bg_color_team;
1356                         pos = HUD_Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1357                 }
1358                 panel_bg_color = Team_ColorRGB(myteam) * panel_bg_color_team;
1359         }
1360         else
1361         {
1362                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1363                 {
1364                         if(tm.team == NUM_SPECTATOR)
1365                                 continue;
1366                         if(!tm.team && teamplay)
1367                                 continue;
1368
1369                         pos = HUD_Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1370                 }
1371         }
1372
1373         if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1374                 if(race_speedaward) {
1375                         drawcolorcodedstring(pos, sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, race_speedaward_unit, race_speedaward_holder), hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1376                         pos.y += 1.25 * hud_fontsize.y;
1377                 }
1378                 if(race_speedaward_alltimebest) {
1379                         drawcolorcodedstring(pos, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest, race_speedaward_alltimebest_unit, race_speedaward_alltimebest_holder), hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1380                         pos.y += 1.25 * hud_fontsize.y;
1381                 }
1382                 pos = HUD_DrawScoreboardRankings(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1383         }
1384         else if (autocvar_hud_panel_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL)
1385                 pos = HUD_DrawScoreboardAccuracyStats(pos, panel_bg_color, bg_size);
1386
1387         pos = HUD_DrawMapStats(pos, panel_bg_color, bg_size);
1388
1389         // List spectators
1390         float specs = 0;
1391         tmp = pos;
1392         for(pl = players.sort_next; pl; pl = pl.sort_next)
1393         {
1394                 if(pl.team != NUM_SPECTATOR)
1395                         continue;
1396                 pos.y += 1.25 * hud_fontsize.y;
1397                 HUD_PrintScoreboardItem(pos, panel_bg_color, pl, (pl.sv_entnum == player_localnum), specs);
1398                 ++specs;
1399         }
1400
1401         if(specs)
1402         {
1403                 draw_beginBoldFont();
1404                 drawstring(tmp, _("Spectators"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1405                 draw_endBoldFont();
1406                 pos.y += 1.25 * hud_fontsize.y;
1407         }
1408
1409         // Print info string
1410         float tl, fl, ll;
1411         str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1412         tl = STAT(TIMELIMIT);
1413         fl = STAT(FRAGLIMIT);
1414         ll = STAT(LEADLIMIT);
1415         if(gametype == MAPINFO_TYPE_LMS)
1416         {
1417                 if(tl > 0)
1418                         str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1419         }
1420         else
1421         {
1422                 if(tl > 0)
1423                         str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1424                 if(fl > 0)
1425                 {
1426                         if(tl > 0)
1427                                 str = strcat(str, _(" or"));
1428                         if(teamplay)
1429                         {
1430                                 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags[ts_primary], fl),
1431                                         (teamscores_label[ts_primary] == "score")   ? CTX(_("SCO^points")) :
1432                                         (teamscores_label[ts_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1433                                         TranslateScoresLabel(teamscores_label[ts_primary])));
1434                         }
1435                         else
1436                         {
1437                                 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags[ps_primary], fl),
1438                                         (scores_label[ps_primary] == "score")   ? CTX(_("SCO^points")) :
1439                                         (scores_label[ps_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1440                                         TranslateScoresLabel(scores_label[ps_primary])));
1441                         }
1442                 }
1443                 if(ll > 0)
1444                 {
1445                         if(tl > 0 || fl > 0)
1446                                 str = strcat(str, _(" or"));
1447                         if(teamplay)
1448                         {
1449                                 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags[ts_primary], ll),
1450                                         (teamscores_label[ts_primary] == "score")   ? CTX(_("SCO^points")) :
1451                                         (teamscores_label[ts_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1452                                         TranslateScoresLabel(teamscores_label[ts_primary])));
1453                         }
1454                         else
1455                         {
1456                                 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags[ps_primary], ll),
1457                                         (scores_label[ps_primary] == "score")   ? CTX(_("SCO^points")) :
1458                                         (scores_label[ps_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1459                                         TranslateScoresLabel(scores_label[ps_primary])));
1460                         }
1461                 }
1462         }
1463
1464         pos.y += 1.2 * hud_fontsize.y;
1465         drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1466
1467         // print information about respawn status
1468         float respawn_time = STAT(RESPAWN_TIME);
1469         if(!intermission)
1470         if(respawn_time)
1471         {
1472                 if(respawn_time < 0)
1473                 {
1474                         // a negative number means we are awaiting respawn, time value is still the same
1475                         respawn_time *= -1; // remove mark now that we checked it
1476                         respawn_time = max(time, respawn_time); // don't show a negative value while the server is respawning the player (lag)
1477
1478                         str = sprintf(_("^1Respawning in ^3%s^1..."),
1479                                 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1480                                         count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1481                                         :
1482                                         count_seconds(respawn_time - time)
1483                                 )
1484                         );
1485                 }
1486                 else if(time < respawn_time)
1487                 {
1488                         str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1489                                 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1490                                         count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1491                                         :
1492                                         count_seconds(respawn_time - time)
1493                                 )
1494                         );
1495                 }
1496                 else if(time >= respawn_time)
1497                         str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1498
1499                 pos.y += 1.2 * hud_fontsize.y;
1500                 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1501         }
1502
1503         scoreboard_bottom = pos.y + 2 * hud_fontsize.y;
1504 }