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