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