]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/client/scoreboard.qc
Merge branch 'master' into Mario/monsters_broken
[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         LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command.\n"));
249         LOG_INFO(_("^3|---------------------------------------------------------------|\n"));
250         LOG_INFO(_("Usage:\n"));
251         LOG_INFO(_("^2scoreboard_columns_set default\n"));
252         LOG_INFO(_("^2scoreboard_columns_set ^7field1 field2 ...\n"));
253         LOG_INFO(_("The following field names are recognized (case insensitive):\n"));
254         LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields.\n\n"));
255
256         LOG_INFO(_("^3name^7 or ^3nick^7             Name of a player\n"));
257         LOG_INFO(_("^3ping^7                     Ping time\n"));
258         LOG_INFO(_("^3pl^7                       Packet loss\n"));
259         LOG_INFO(_("^3kills^7                    Number of kills\n"));
260         LOG_INFO(_("^3deaths^7                   Number of deaths\n"));
261         LOG_INFO(_("^3suicides^7                 Number of suicides\n"));
262         LOG_INFO(_("^3frags^7                    kills - suicides\n"));
263         LOG_INFO(_("^3kd^7                       The kill-death ratio\n"));
264         LOG_INFO(_("^3sum^7                      frags - deaths\n"));
265         LOG_INFO(_("^3caps^7                     How often a flag (CTF) or a key (KeyHunt) was captured\n"));
266         LOG_INFO(_("^3pickups^7                  How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up\n"));
267         LOG_INFO(_("^3captime^7                  Time of fastest cap (CTF)\n"));
268         LOG_INFO(_("^3fckills^7                  Number of flag carrier kills\n"));
269         LOG_INFO(_("^3returns^7                  Number of flag returns\n"));
270         LOG_INFO(_("^3drops^7                    Number of flag drops\n"));
271         LOG_INFO(_("^3lives^7                    Number of lives (LMS)\n"));
272         LOG_INFO(_("^3rank^7                     Player rank\n"));
273         LOG_INFO(_("^3pushes^7                   Number of players pushed into void\n"));
274         LOG_INFO(_("^3destroyed^7                Number of keys destroyed by pushing them into void\n"));
275         LOG_INFO(_("^3kckills^7                  Number of keys carrier kills\n"));
276         LOG_INFO(_("^3losses^7                   Number of times a key was lost\n"));
277         LOG_INFO(_("^3laps^7                     Number of laps finished (race/cts)\n"));
278         LOG_INFO(_("^3time^7                     Total time raced (race/cts)\n"));
279         LOG_INFO(_("^3fastest^7                  Time of fastest lap (race/cts)\n"));
280         LOG_INFO(_("^3ticks^7                    Number of ticks (DOM)\n"));
281         LOG_INFO(_("^3takes^7                    Number of domination points taken (DOM)\n"));
282         LOG_INFO(_("^3bckills^7                  Number of ball carrier kills\n"));
283         LOG_INFO(_("^3bctime^7                   Total amount of time holding the ball in Keepaway\n"));
284         LOG_INFO(_("^3score^7                    Total score\n\n"));
285
286         LOG_INFO(_("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         LOG_INFO(_("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         LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4\n"));
295         LOG_INFO(_("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         LOG_INFO(_("'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                                         LOG_INFOF("^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                         LOG_INFO("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                                 LOG_INFO("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                         LOG_INFO("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                         LOG_INFOF("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                         LOG_INFOF("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 (HUD_Radar_Clickable())
963                 return 0;
964         else if (scoreboard_showscores)
965                 return 1;
966         else if (intermission == 1)
967                 return 1;
968         else if (intermission == 2)
969                 return 0;
970         else if (spectatee_status != -1 && getstati(STAT_HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
971                 return 1;
972         else if (scoreboard_showscores_force)
973                 return 1;
974         return 0;
975 }
976
977 float average_accuracy;
978 vector HUD_DrawScoreboardAccuracyStats(vector pos, vector rgb, vector bg_size)
979 {
980         WepSet weapons_stat = WepSet_GetFromStat();
981         WepSet weapons_inmap = WepSet_GetFromStat_InMap();
982         float initial_posx = pos.x;
983         int i;
984         float weapon_stats;
985         int disownedcnt = 0;
986         for(i = WEP_FIRST; i <= WEP_LAST; ++i)
987         {
988                 self = get_weaponinfo(i);
989                 if(!self.weapon)
990                         continue;
991
992                 weapon_stats = weapon_accuracy[i-WEP_FIRST];
993
994                 if(weapon_stats < 0 && !(weapons_stat & WepSet_FromWeapon(i) || weapons_inmap & WepSet_FromWeapon(i)))
995                         ++disownedcnt;
996         }
997
998         int weapon_cnt = WEP_COUNT - disownedcnt;
999
1000         if(weapon_cnt <= 0)
1001                 return pos;
1002
1003         int rows;
1004         if(autocvar_scoreboard_accuracy_doublerows && weapon_cnt >= floor(WEP_COUNT * 0.5))
1005                 rows = 2;
1006         else
1007                 rows = 1;
1008         int columnns = ceil(weapon_cnt / rows);
1009
1010         float height = 40;
1011         float fontsize = height * 1/3;
1012         float weapon_height = height * 2/3;
1013         float weapon_width = sbwidth / columnns / rows;
1014
1015         drawstring(pos, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1016         pos.y += 1.25 * hud_fontsize.y + autocvar_scoreboard_border_thickness;
1017         vector tmp = '0 0 0';
1018         tmp.x = sbwidth;
1019         tmp.y = height * rows;
1020
1021         if (teamplay)
1022                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb * autocvar_scoreboard_color_bg_team, scoreboard_alpha_bg, DRAWFLAG_NORMAL);
1023         else
1024                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, scoreboard_alpha_bg, DRAWFLAG_NORMAL);
1025         drawborderlines(autocvar_scoreboard_border_thickness, pos, tmp, '0 0 0', scoreboard_alpha_bg * 0.75, DRAWFLAG_NORMAL);
1026
1027         // column highlighting
1028         for(i = 0; i < columnns; ++i)
1029         {
1030                 if(!(i % 2))
1031                         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);
1032         }
1033
1034         // row highlighting
1035         for(i = 0; i < rows; ++i)
1036         {
1037                 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);
1038         }
1039
1040         average_accuracy = 0;
1041         int weapons_with_stats = 0;
1042         if(rows == 2)
1043                 pos.x += weapon_width / 2;
1044
1045         if(autocvar_scoreboard_accuracy_nocolors)
1046                 rgb = '1 1 1';
1047         else
1048                 Accuracy_LoadColors();
1049
1050         float oldposx = pos.x;
1051         vector tmpos = pos;
1052
1053         int column;
1054         for(i = WEP_FIRST, column = 0; i <= WEP_LAST; ++i)
1055         {
1056                 self = get_weaponinfo(i);
1057                 if (!self.weapon)
1058                         continue;
1059                 weapon_stats = weapon_accuracy[i-WEP_FIRST];
1060
1061                 if(weapon_stats < 0 && !(weapons_stat & WepSet_FromWeapon(i) || weapons_inmap & WepSet_FromWeapon(i)))
1062                         continue;
1063
1064                 float weapon_alpha;
1065                 if(weapon_stats >= 0)
1066                         weapon_alpha = scoreboard_alpha_fg;
1067                 else
1068                         weapon_alpha = 0.2 * scoreboard_alpha_fg;
1069
1070                 // weapon icon
1071                 drawpic_aspect_skin(tmpos, self.model2, '1 0 0' * weapon_width + '0 1 0' * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1072                 // the accuracy
1073                 if(weapon_stats >= 0) {
1074                         weapons_with_stats += 1;
1075                         average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1076
1077                         string s;
1078                         s = sprintf("%d%%", weapon_stats*100);
1079
1080                         float padding;
1081                         padding = (weapon_width - stringwidth(s, false, '1 0 0' * fontsize)) / 2; // center the accuracy value
1082
1083                         if(!autocvar_scoreboard_accuracy_nocolors)
1084                                 rgb = Accuracy_GetColor(weapon_stats);
1085
1086                         drawstring(tmpos + '1 0 0' * padding + '0 1 0' * weapon_height, s, '1 1 0' * fontsize, rgb, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1087                 }
1088                 tmpos.x += weapon_width * rows;
1089                 pos.x += weapon_width * rows;
1090                 if(rows == 2 && column == columnns - 1) {
1091                         tmpos.x = oldposx;
1092                         tmpos.y += height;
1093                         pos.y += height;
1094                 }
1095                 ++column;
1096         }
1097
1098         if(weapons_with_stats)
1099                 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1100
1101         pos.y += height;
1102         pos.y += 1.25 * hud_fontsize.y;
1103         pos.x = initial_posx;
1104         return pos;
1105 }
1106
1107 vector HUD_DrawKeyValue(vector pos, string key, string value) {
1108         float px = pos.x;
1109         pos.x += hud_fontsize.x * 0.25;
1110         drawstring(pos, key, hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1111         pos.x = xmax - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1112         drawstring(pos, value, hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1113         pos.x = px;
1114         pos.y+= hud_fontsize.y;
1115
1116         return pos;
1117 }
1118
1119 vector HUD_DrawMapStats(vector pos, vector rgb, vector bg_size) {
1120         float stat_secrets_found, stat_secrets_total;
1121         float stat_monsters_killed, stat_monsters_total;
1122         float rows = 0;
1123         string val;
1124
1125         // get monster stats
1126         stat_monsters_killed = getstatf(STAT_MONSTERS_KILLED);
1127         stat_monsters_total = getstatf(STAT_MONSTERS_TOTAL);
1128
1129         // get secrets stats
1130         stat_secrets_found = getstatf(STAT_SECRETS_FOUND);
1131         stat_secrets_total = getstatf(STAT_SECRETS_TOTAL);
1132
1133         // get number of rows
1134         if(stat_secrets_total)
1135                 rows += 1;
1136         if(stat_monsters_total)
1137                 rows += 1;
1138
1139         // if no rows, return
1140         if (!rows)
1141                 return pos;
1142
1143         //  draw table header
1144         drawstring(pos, _("Map stats:"), hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1145         pos.y += 1.25 * hud_fontsize.y + autocvar_scoreboard_border_thickness;
1146
1147         // draw table
1148         vector tmp = '0 0 0';
1149         tmp.x = sbwidth;
1150         tmp.y = hud_fontsize.y * rows;
1151
1152         if (teamplay)
1153                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb * autocvar_scoreboard_color_bg_team, scoreboard_alpha_bg, DRAWFLAG_NORMAL);
1154         else
1155                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, scoreboard_alpha_bg, DRAWFLAG_NORMAL);
1156         drawborderlines(autocvar_scoreboard_border_thickness, pos, tmp, '0 0 0', scoreboard_alpha_bg * 0.75, DRAWFLAG_NORMAL);
1157
1158         // draw monsters
1159         if(stat_monsters_total)
1160         {
1161                 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1162                 pos = HUD_DrawKeyValue(pos, _("Monsters killed:"), val);
1163         }
1164
1165         // draw secrets
1166         if(stat_secrets_total)
1167         {
1168                 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1169                 pos = HUD_DrawKeyValue(pos, _("Secrets found:"), val);
1170         }
1171
1172         // update position
1173         pos.y += 1.25 * hud_fontsize.y;
1174         return pos;
1175 }
1176
1177
1178 vector HUD_DrawScoreboardRankings(vector pos, entity pl,  vector rgb, vector bg_size)
1179 {
1180         int i;
1181         RANKINGS_RECEIVED_CNT = 0;
1182         for (i=RANKINGS_CNT-1; i>=0; --i)
1183                 if (grecordtime[i])
1184                         ++RANKINGS_RECEIVED_CNT;
1185
1186         if (RANKINGS_RECEIVED_CNT == 0)
1187                 return pos;
1188
1189         float is_spec;
1190         is_spec = (GetPlayerColor(pl.sv_entnum) == NUM_SPECTATOR);
1191         vector hl_rgb;
1192         hl_rgb.x = autocvar_scoreboard_color_bg_r + 0.5;
1193         hl_rgb.y = autocvar_scoreboard_color_bg_g + 0.5;
1194         hl_rgb.z = autocvar_scoreboard_color_bg_b + 0.5;
1195
1196         pos.y += hud_fontsize.y;
1197         drawstring(pos, _("Rankings"), hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1198         pos.y += hud_fontsize.y + autocvar_scoreboard_border_thickness;
1199         vector tmp = '0 0 0';
1200         tmp.x = sbwidth;
1201         tmp.y = 1.25 * hud_fontsize.y * RANKINGS_RECEIVED_CNT;
1202
1203         if (teamplay)
1204                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb * autocvar_scoreboard_color_bg_team, scoreboard_alpha_bg, DRAWFLAG_NORMAL);
1205         else
1206                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, scoreboard_alpha_bg, DRAWFLAG_NORMAL);
1207         drawborderlines(autocvar_scoreboard_border_thickness, pos, tmp, '0 0 0', scoreboard_alpha_bg * 0.75, DRAWFLAG_NORMAL);
1208
1209         // row highlighting
1210         for(i = 0; i<RANKINGS_RECEIVED_CNT; ++i)
1211         {
1212                 string n, p;
1213                 float t;
1214                 t = grecordtime[i];
1215                 if (t == 0)
1216                         continue;
1217                 n = grecordholder[i];
1218                 p = count_ordinal(i+1);
1219                 if(grecordholder[i] == GetPlayerName(player_localnum))
1220                         drawfill(pos, '1 0 0' * sbwidth + '0 1.25 0' * hud_fontsize.y, hl_rgb, scoreboard_highlight_alpha_self, DRAWFLAG_NORMAL);
1221                 else if(!(i % 2) && scoreboard_highlight)
1222                         drawfill(pos, '1 0 0' * sbwidth + '0 1.25 0' * hud_fontsize.y, hl_rgb, scoreboard_highlight_alpha, DRAWFLAG_NORMAL);
1223                 drawstring(pos, p, '1 1 0' * hud_fontsize.y, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1224                 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);
1225                 drawcolorcodedstring(pos + '8 0 0' * hud_fontsize.y, n, '1 1 0' * hud_fontsize.y, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1226                 pos.y += 1.25 * hud_fontsize.y;
1227         }
1228         pos.y += autocvar_scoreboard_border_thickness;
1229
1230         return pos;
1231 }
1232
1233 float hud_woulddrawscoreboard_prev;
1234 float hud_woulddrawscoreboard_change; // "time" at which HUD_WouldDrawScoreboard() changed
1235 void HUD_DrawScoreboard()
1236 {
1237         float hud_woulddrawscoreboard;
1238         hud_woulddrawscoreboard = scoreboard_active;
1239         if(hud_woulddrawscoreboard != hud_woulddrawscoreboard_prev) {
1240                 hud_woulddrawscoreboard_change = time;
1241                 hud_woulddrawscoreboard_prev = hud_woulddrawscoreboard;
1242         }
1243
1244         if(hud_woulddrawscoreboard) {
1245                 float scoreboard_fadeinspeed = autocvar_scoreboard_fadeinspeed;
1246                 if (scoreboard_fadeinspeed)
1247                         scoreboard_fade_alpha = bound (0, (time - hud_woulddrawscoreboard_change) * scoreboard_fadeinspeed, 1);
1248                 else
1249                         scoreboard_fade_alpha = 1;
1250         }
1251         else {
1252                 float scoreboard_fadeoutspeed = autocvar_scoreboard_fadeoutspeed;
1253                 if (scoreboard_fadeoutspeed)
1254                         scoreboard_fade_alpha = bound (0, (1/scoreboard_fadeoutspeed - (time - hud_woulddrawscoreboard_change)) * scoreboard_fadeoutspeed, 1);
1255                 else
1256                         scoreboard_fade_alpha = 0;
1257         }
1258
1259         if (!scoreboard_fade_alpha)
1260                 return;
1261
1262         HUD_UpdatePlayerTeams();
1263
1264         scoreboard_alpha_bg = autocvar_scoreboard_alpha_bg * scoreboard_fade_alpha * (1 - autocvar__menu_alpha);
1265         scoreboard_alpha_fg = autocvar_scoreboard_alpha_fg * scoreboard_fade_alpha * (1 - autocvar__menu_alpha);
1266         scoreboard_highlight = autocvar_scoreboard_highlight;
1267         scoreboard_highlight_alpha = autocvar_scoreboard_highlight_alpha * scoreboard_alpha_fg;
1268         scoreboard_highlight_alpha_self = autocvar_scoreboard_highlight_alpha_self * scoreboard_alpha_fg;
1269         scoreboard_alpha_name = autocvar_scoreboard_alpha_name * scoreboard_alpha_fg;
1270         scoreboard_alpha_name_self = autocvar_scoreboard_alpha_name_self * scoreboard_alpha_fg;
1271
1272         vector rgb, pos, tmp;
1273         entity pl, tm;
1274         string str;
1275
1276         xmin = (autocvar_scoreboard_offset_left * vid_conwidth);
1277         ymin = max((autocvar_con_notify * autocvar_con_notifysize), (autocvar_scoreboard_offset_vertical * vid_conwidth));
1278
1279         xmax = ((1 - autocvar_scoreboard_offset_right) * vid_conwidth);
1280         ymax = (vid_conheight - ymin);
1281
1282         sbwidth = xmax - xmin;
1283
1284         // Initializes position
1285         pos.x = xmin;
1286         pos.y = ymin;
1287         pos.z = 0;
1288
1289         // Heading
1290         vector sb_heading_fontsize;
1291         sb_heading_fontsize = hud_fontsize * 2;
1292         draw_beginBoldFont();
1293         drawstring(pos, _("Scoreboard"), sb_heading_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1294         draw_endBoldFont();
1295
1296         pos.y += sb_heading_fontsize.y + hud_fontsize.y * 0.25;
1297
1298         // Draw the scoreboard
1299         vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * autocvar_scoreboard_bg_scale;
1300
1301         if(teamplay)
1302         {
1303                 vector team_score_baseoffset;
1304                 team_score_baseoffset = eY * (2 * autocvar_scoreboard_border_thickness + hud_fontsize.y) - eX * (autocvar_scoreboard_border_thickness + hud_fontsize.x * 0.25);
1305                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1306                 {
1307                         if(tm.team == NUM_SPECTATOR)
1308                                 continue;
1309
1310                         draw_beginBoldFont();
1311                         rgb = Team_ColorRGB(tm.team);
1312                         str = ftos(tm.(teamscores[ts_primary]));
1313                         drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1314
1315                         if(ts_primary != ts_secondary)
1316                         {
1317                                 str = ftos(tm.(teamscores[ts_secondary]));
1318                                 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);
1319                         }
1320                         draw_endBoldFont();
1321
1322                         pos = HUD_Scoreboard_MakeTable(pos, tm, rgb, bg_size);
1323                 }
1324                 rgb.x = autocvar_scoreboard_color_bg_r;
1325                 rgb.y = autocvar_scoreboard_color_bg_g;
1326                 rgb.z = autocvar_scoreboard_color_bg_b;
1327         }
1328         else
1329         {
1330                 rgb.x = autocvar_scoreboard_color_bg_r;
1331                 rgb.y = autocvar_scoreboard_color_bg_g;
1332                 rgb.z = autocvar_scoreboard_color_bg_b;
1333
1334                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1335                 {
1336                         if(tm.team == NUM_SPECTATOR)
1337                                 continue;
1338
1339                         pos = HUD_Scoreboard_MakeTable(pos, tm, rgb, bg_size);
1340                 }
1341         }
1342
1343         if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1344                 if(race_speedaward) {
1345                         drawcolorcodedstring(pos, sprintf(_("Speed award: %d ^7(%s^7)"), race_speedaward, race_speedaward_holder), hud_fontsize, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1346                         pos.y += 1.25 * hud_fontsize.y;
1347                 }
1348                 if(race_speedaward_alltimebest) {
1349                         drawcolorcodedstring(pos, sprintf(_("All-time fastest: %d ^7(%s^7)"), race_speedaward_alltimebest, race_speedaward_alltimebest_holder), hud_fontsize, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1350                         pos.y += 1.25 * hud_fontsize.y;
1351                 }
1352                 pos = HUD_DrawScoreboardRankings(pos, playerslots[player_localnum], rgb, bg_size);
1353         }
1354         else if(autocvar_scoreboard_accuracy && spectatee_status == 0 && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL) {
1355                 if(teamplay)
1356                         pos = HUD_DrawScoreboardAccuracyStats(pos, Team_ColorRGB(myteam), bg_size);
1357                 else
1358                         pos = HUD_DrawScoreboardAccuracyStats(pos, rgb, bg_size);
1359         }
1360
1361
1362         if(teamplay)
1363                 pos = HUD_DrawMapStats(pos, Team_ColorRGB(myteam), bg_size);
1364         else
1365                 pos = HUD_DrawMapStats(pos, rgb, bg_size);
1366
1367         // List spectators
1368         float specs;
1369         specs = 0;
1370         tmp = pos;
1371         vector item_size;
1372         item_size.x = sbwidth;
1373         item_size.y = hud_fontsize.y * 1.25;
1374         item_size.z = 0;
1375         for(pl = players.sort_next; pl; pl = pl.sort_next)
1376         {
1377                 if(pl.team != NUM_SPECTATOR)
1378                         continue;
1379                 pos.y += 1.25 * hud_fontsize.y;
1380                 HUD_PrintScoreboardItem(pos, item_size, pl, (pl.sv_entnum == player_localnum), specs);
1381                 ++specs;
1382         }
1383
1384         if(specs)
1385         {
1386                 draw_beginBoldFont();
1387                 drawstring(tmp, _("Spectators"), hud_fontsize, '1 1 1', scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1388                 draw_endBoldFont();
1389                 pos.y += 1.25 * hud_fontsize.y;
1390         }
1391
1392         // Print info string
1393         float tl, fl, ll;
1394         str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1395         tl = getstatf(STAT_TIMELIMIT);
1396         fl = getstatf(STAT_FRAGLIMIT);
1397         ll = getstatf(STAT_LEADLIMIT);
1398         if(gametype == MAPINFO_TYPE_LMS)
1399         {
1400                 if(tl > 0)
1401                         str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1402         }
1403         else
1404         {
1405                 if(tl > 0)
1406                         str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1407                 if(fl > 0)
1408                 {
1409                         if(tl > 0)
1410                                 str = strcat(str, _(" or"));
1411                         if(teamplay)
1412                         {
1413                                 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags[ts_primary], fl),
1414                                         (teamscores_label[ts_primary] == "score")   ? CTX(_("SCO^points")) :
1415                                         (teamscores_label[ts_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1416                                         TranslateScoresLabel(teamscores_label[ts_primary])));
1417                         }
1418                         else
1419                         {
1420                                 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags[ps_primary], fl),
1421                                         (scores_label[ps_primary] == "score")   ? CTX(_("SCO^points")) :
1422                                         (scores_label[ps_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1423                                         TranslateScoresLabel(scores_label[ps_primary])));
1424                         }
1425                 }
1426                 if(ll > 0)
1427                 {
1428                         if(tl > 0 || fl > 0)
1429                                 str = strcat(str, _(" or"));
1430                         if(teamplay)
1431                         {
1432                                 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags[ts_primary], ll),
1433                                         (teamscores_label[ts_primary] == "score")   ? CTX(_("SCO^points")) :
1434                                         (teamscores_label[ts_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1435                                         TranslateScoresLabel(teamscores_label[ts_primary])));
1436                         }
1437                         else
1438                         {
1439                                 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags[ps_primary], ll),
1440                                         (scores_label[ps_primary] == "score")   ? CTX(_("SCO^points")) :
1441                                         (scores_label[ps_primary] == "fastest") ? CTX(_("SCO^is beaten")) :
1442                                         TranslateScoresLabel(scores_label[ps_primary])));
1443                         }
1444                 }
1445         }
1446
1447         pos.y += 1.2 * hud_fontsize.y;
1448         drawcolorcodedstring(pos + '0.5 0 0' * (sbwidth - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1449
1450         // print information about respawn status
1451         float respawn_time = getstatf(STAT_RESPAWN_TIME);
1452         if(!intermission)
1453         if(respawn_time)
1454         {
1455                 if(respawn_time < 0)
1456                 {
1457                         // a negative number means we are awaiting respawn, time value is still the same
1458                         respawn_time *= -1; // remove mark now that we checked it
1459                         respawn_time = max(time, respawn_time); // don't show a negative value while the server is respawning the player (lag)
1460
1461                         str = sprintf(_("^1Respawning in ^3%s^1..."),
1462                                 (autocvar_scoreboard_respawntime_decimals ?
1463                                         count_seconds_decs(respawn_time - time, autocvar_scoreboard_respawntime_decimals)
1464                                         :
1465                                         count_seconds(respawn_time - time)
1466                                 )
1467                         );
1468                 }
1469                 else if(time < respawn_time)
1470                 {
1471                         str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1472                                 (autocvar_scoreboard_respawntime_decimals ?
1473                                         count_seconds_decs(respawn_time - time, autocvar_scoreboard_respawntime_decimals)
1474                                         :
1475                                         count_seconds(respawn_time - time)
1476                                 )
1477                         );
1478                 }
1479                 else if(time >= respawn_time)
1480                         str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1481
1482                 pos.y += 1.2 * hud_fontsize.y;
1483                 drawcolorcodedstring(pos + '0.5 0 0' * (sbwidth - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, scoreboard_alpha_fg, DRAWFLAG_NORMAL);
1484         }
1485
1486         scoreboard_bottom = pos.y + 2 * hud_fontsize.y;
1487 }