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