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