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