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