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